Estas notas estan basadas en el libro Graphical Data Analysis with R, la bibliografía se encuentra en el temario
Es el PRIMER paso en el análisis de datos, es un punto CRÍTICO para realizar un análisis correcto -con contexto, sin sesgo, desde diferentes puntos de vista-
Puedes pensarlo como la primera aproximación al problema que quieres resolver
Como verás ayuda a hacer un modelado más robusto
De manera no estricta, si un análisis de datos no incluye modelado estadístico formal y/o inferencia/predicción entonces el análisis es EDA
Normalmente los datos que tenemos para analizar vienen en un formato rectangular (..aunque nunca limpios ni cómo los necesitamos (╯°□°)╯︵ ┻━┻, pero lo veremos más adelante) donde un renglón es una observación de un experimento y cada columna es: el identificador del sujeto, la variable de salida y las variables explicativas.
Analizar los datos de esta tabla resulta tedioso, aburrido y abrumador de entender, enter EDA :), las técnicas utilizadas en el EDA permiten esconder ciertas cosas de los datos para hacer sobresalir o dejar claras otras.
Hay 2 grandes maneras cruzadas de clasificar el tipo de EDA:
Se recomienda primero hacer un EDA univariado a cada variable que forme parte de un EDA multivariado ANTES de hacer el EDA multivariado
Después de clasificar en estos 4 tipos cruzados existen más divisiones al EDA basadas en:
Aunque es una visualización muy solicitada por gente de negocio, existe una mejor manera de representar la misma información. El objetivo de una visualización de pie es mostrar cómo un 100% se distribuye entre diferentes valores de una variable. Una mejor manera de visualizar la misma información es haciendo una grfáica de barras horizontales ordenando de la proporción más grande a la más pequeña, el eje x tiene que ir de 0 a 100%.
Si no te queda de otra más que presentar un pie, asegúrate que las proporciones suman 100%!, ordena las divisiones de mayor a menor…
¿Algo malo aquí?
* Imagen tomada de flowingdata.com
¿Qué? (╯°□°)╯︵ ┻━┻
* Imagen tomada de flowingdata.com
Paren!!!! (╯°□°)╯︵ ┻━┻
* Imagen tomada de flowingdata.com
Moraleja: Pon atención a tus visualizaciones son TAN IMPORTANTES como los modelos que realizas, es tu responsabilidad presentar información precisa, sin sesgo y que permita a los demás tomar decisiones basadas en ellas.
Como NO hacer una gráfica de barras
* Imagen tomada de viz.wtf
No hagan esto!!!! (╯°□°)╯︵ ┻━┻ \(\rightarrow\) Por eso en esta clase están prohibidos los pie!!!
* Imagen tomada de viz.wtf
¿Qué podemos encontrar?
\(\rightarrow\) Generar una tabla de frecuencias de cada categoría. Por ejemplo:
| category | count | proportion |
|---|---|---|
| girls | 2 | 0.1333333 |
| boys | 4 | 0.2666667 |
| women | 3 | 0.2000000 |
| man | 6 | 0.4000000 |
| total | 15 | 1.0000000 |
Es muy importante obtener los totales para identificar errores, faltantes, anomalías en los datos
Quisieramos conocer algunas métricas de centralidad: modalidad (número de modas), mediana, media; dispersión: desviación estándar, forma -distribución teórica- para identificar si tiene colas pesadas: *skewness -medida de asimetría- e identificación de outliers.
Skewness: valores cercanos a 0 indican muy poco skewness, si el número es negativo la cola es a la izquierda, si el número es positivo la cola es a la derecha
La mayoría de estas métricas las puedes obtener con un summary en R. Por ejemplo:
data(mtcars)
glimpse(mtcars)## Observations: 32
## Variables: 11
## $ mpg <dbl> 21.0, 21.0, 22.8, 21.4, 18.7, 18.1, 14.3, 24.4, 22.8, 19....
## $ cyl <dbl> 6, 6, 4, 6, 8, 6, 8, 4, 4, 6, 6, 8, 8, 8, 8, 8, 8, 4, 4, ...
## $ disp <dbl> 160.0, 160.0, 108.0, 258.0, 360.0, 225.0, 360.0, 146.7, 1...
## $ hp <dbl> 110, 110, 93, 110, 175, 105, 245, 62, 95, 123, 123, 180, ...
## $ drat <dbl> 3.90, 3.90, 3.85, 3.08, 3.15, 2.76, 3.21, 3.69, 3.92, 3.9...
## $ wt <dbl> 2.620, 2.875, 2.320, 3.215, 3.440, 3.460, 3.570, 3.190, 3...
## $ qsec <dbl> 16.46, 17.02, 18.61, 19.44, 17.02, 20.22, 15.84, 20.00, 2...
## $ vs <dbl> 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, ...
## $ am <dbl> 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, ...
## $ gear <dbl> 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, ...
## $ carb <dbl> 4, 4, 1, 1, 2, 1, 4, 2, 2, 4, 4, 3, 3, 3, 4, 4, 4, 1, 2, ...
summary(mtcars)## mpg cyl disp hp
## Min. :10.40 Min. :4.000 Min. : 71.1 Min. : 52.0
## 1st Qu.:15.43 1st Qu.:4.000 1st Qu.:120.8 1st Qu.: 96.5
## Median :19.20 Median :6.000 Median :196.3 Median :123.0
## Mean :20.09 Mean :6.188 Mean :230.7 Mean :146.7
## 3rd Qu.:22.80 3rd Qu.:8.000 3rd Qu.:326.0 3rd Qu.:180.0
## Max. :33.90 Max. :8.000 Max. :472.0 Max. :335.0
## drat wt qsec vs
## Min. :2.760 Min. :1.513 Min. :14.50 Min. :0.0000
## 1st Qu.:3.080 1st Qu.:2.581 1st Qu.:16.89 1st Qu.:0.0000
## Median :3.695 Median :3.325 Median :17.71 Median :0.0000
## Mean :3.597 Mean :3.217 Mean :17.85 Mean :0.4375
## 3rd Qu.:3.920 3rd Qu.:3.610 3rd Qu.:18.90 3rd Qu.:1.0000
## Max. :4.930 Max. :5.424 Max. :22.90 Max. :1.0000
## am gear carb
## Min. :0.0000 Min. :3.000 Min. :1.000
## 1st Qu.:0.0000 1st Qu.:3.000 1st Qu.:2.000
## Median :0.0000 Median :4.000 Median :2.000
## Mean :0.4062 Mean :3.688 Mean :2.812
## 3rd Qu.:1.0000 3rd Qu.:4.000 3rd Qu.:4.000
## Max. :1.0000 Max. :5.000 Max. :8.000
En el análisis multivariado lo que queremos encontrar son relaciones entre varias columnas -normalmente 2-
Por ejemplo:
| Edad/Sexo | Mujer | Hombre | Total |
|---|---|---|---|
| Jóven | 2 | 3 | 5 |
| Adulto | 3 | 5 | 8 |
| Adulto mayor | 4 | 2 | 6 |
| Total | 9 | 10 | 19 |
Lo más común es obtener la correlación y la covarianza entre 2 variables numéricas. Con la covarianza queremos ver qué tanto cambia una variable si la otra lo hace.
Es más sencillo identificar estos cambios con la correlación, ya que esta va de [-1,1] donde -1 indica que las variables tiene una correlación lineal perfecta negativa, 1 indica que las variables tiene una correlación lineal perfecta positiva y 0 que las variables no tienen correlación. De nuevo, estas métricas son más sencillas de analizar visualmente-.
Cuando se tienen más de 2 variables numéricas normalmente se realizan matrices de covarianzas y correlaciones.
¿Qué podríamos encontrar?
¿Cómo podríamos visualizar estas características?
El siguiente histograma corresponde a las velocidades del camponato mundial de ski del 2011, ¿qué puedes decir de la gráfica?
Con el siguiente histograma ¿cambia en algo la historia/conclusión/opinión que tenías con el histograma anterior?
es por esto que debes hacer un análisis exploratorio profundo, ver todos los puntos de vista posibles -diferentes gráficas, diferentes análisis-
¿Qué puedes decir de esta gráfica?
¿Y ahora?
Utilizaremos el set de datos de precios de vivienda de Boston que viene en el paquete MASS, este set de datos contiene 14 variables y 506 observaciones de áreas alrededor de la ciudad de Boston.
En particular nos interesa revisar la varaible medv
table(Boston$medv)##
## 5 5.6 6.3 7 7.2 7.4 7.5 8.1 8.3 8.4 8.5 8.7 8.8 9.5 9.6
## 2 1 1 2 3 1 1 1 2 2 2 1 2 1 1
## 9.7 10.2 10.4 10.5 10.8 10.9 11 11.3 11.5 11.7 11.8 11.9 12 12.1 12.3
## 1 3 2 2 1 2 1 1 1 2 2 2 1 1 1
## 12.5 12.6 12.7 12.8 13 13.1 13.2 13.3 13.4 13.5 13.6 13.8 13.9 14 14.1
## 1 1 3 1 1 4 1 3 4 2 2 5 2 1 3
## 14.2 14.3 14.4 14.5 14.6 14.8 14.9 15 15.1 15.2 15.3 15.4 15.6 15.7 16
## 1 2 2 3 2 1 3 3 1 3 1 2 5 1 1
## 16.1 16.2 16.3 16.4 16.5 16.6 16.7 16.8 17 17.1 17.2 17.3 17.4 17.5 17.6
## 3 2 1 1 2 2 2 2 1 3 3 1 3 3 1
## 17.7 17.8 17.9 18 18.1 18.2 18.3 18.4 18.5 18.6 18.7 18.8 18.9 19 19.1
## 1 5 1 1 1 3 2 3 4 2 3 2 4 2 4
## 19.2 19.3 19.4 19.5 19.6 19.7 19.8 19.9 20 20.1 20.2 20.3 20.4 20.5 20.6
## 2 5 6 4 5 2 3 4 5 5 2 4 4 3 6
## 20.7 20.8 20.9 21 21.1 21.2 21.4 21.5 21.6 21.7 21.8 21.9 22 22.1 22.2
## 2 3 2 3 2 5 5 2 2 7 2 3 7 1 5
## 22.3 22.4 22.5 22.6 22.7 22.8 22.9 23 23.1 23.2 23.3 23.4 23.5 23.6 23.7
## 2 2 3 5 2 4 4 4 7 4 4 2 1 2 4
## 23.8 23.9 24 24.1 24.2 24.3 24.4 24.5 24.6 24.7 24.8 25 25.1 25.2 25.3
## 4 5 2 3 1 3 4 3 2 3 4 8 1 1 1
## 26.2 26.4 26.5 26.6 26.7 27 27.1 27.5 27.9 28 28.1 28.2 28.4 28.5 28.6
## 1 2 1 3 1 1 2 4 2 1 1 1 2 1 1
## 28.7 29 29.1 29.4 29.6 29.8 29.9 30.1 30.3 30.5 30.7 30.8 31 31.1 31.2
## 3 2 2 1 2 2 1 3 1 1 1 1 1 1 1
## 31.5 31.6 31.7 32 32.2 32.4 32.5 32.7 32.9 33 33.1 33.2 33.3 33.4 33.8
## 2 2 1 2 1 1 1 1 1 1 2 2 1 2 1
## 34.6 34.7 34.9 35.1 35.2 35.4 36 36.1 36.2 36.4 36.5 37 37.2 37.3 37.6
## 1 1 3 1 1 2 1 1 2 1 1 1 1 1 1
## 37.9 38.7 39.8 41.3 41.7 42.3 42.8 43.1 43.5 43.8 44 44.8 45.4 46 46.7
## 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## 48.3 48.5 48.8 50
## 1 1 1 16
Espero que no pienses diferente… pero tratar de sacar información de esta tabla esta difícil, solo podemos ver rápidamente que todos los números están redondeados a 1 decimal, fuera de eso… está complicado. Mejor visualizemos…
ggplot(Boston, aes(x=medv)) +
geom_histogram() +
theme_bw() +
ggtitle("Valor medio de las casas (en miles de dólares")## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Además del warning que regresa R al generar el histograma ¿qué información puedes extraer?
¿Qué pasa si cambiamos el tamaño de los bines?
ggplot(Boston, aes(x=medv)) +
geom_histogram(binwidth=1) +
theme_bw() +
ggtitle("Valor medio de las casas (en miles de dólares")Hay detalles que antes no se percibían, como que en 34 hay una caída que no se había identificado antes
Ahora veamos todas las variables:
B2 <- gather(Boston, bos_vars, bos_values, crim:medv)
ggplot(B2, aes(bos_values)) +
geom_histogram() +
theme_bw() +
facet_wrap(~ bos_vars , scales = "free")## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Este es un buen punto de partida, pero el tamaño de los bines no beneficia a todas las variables, cada variable debe tener su tamaño de bin adecuado, por otro lado la escala varía mucho entre variables: hay unas que van de 0 a 20 y otras de 0 a 400.
?boxplot?stripchart Un scatterplot de 1 dimensión, puede ser una alternativa al boxplot cuando tienes pocos datos?stem?density Necesitarás agregar un plot para ver la gráfica?rug Primero necesitarás generar o el stripchart o el density para agregarle rug#boxplot
ggplot(Boston, aes(x="var", y=medv)) +
geom_boxplot() +
theme_bw() +
coord_flip() +
ggtitle("boxplot variable medv")#stripchart
stripchart(Boston$medv)#stem
stem(Boston$medv)##
## The decimal point is at the |
##
## 4 | 006
## 6 | 30022245
## 8 | 1334455788567
## 10 | 2224455899035778899
## 12 | 013567778011112333444455668888899
## 14 | 0111233445556689990001222344666667
## 16 | 01112234556677880111222344455567888889
## 18 | 01222334445555667778899990011112233333444444555566666778889999
## 20 | 0000011111223333444455566666677888990001122222444445566777777788999
## 22 | 00000001222223344555666667788889999000011111112222333344566777788889
## 24 | 001112333444455566777888800000000123
## 26 | 24456667011555599
## 28 | 01244567770011466889
## 30 | 111357801255667
## 32 | 0024579011223448
## 34 | 679991244
## 36 | 01224502369
## 38 | 78
## 40 | 37
## 42 | 38158
## 44 | 084
## 46 | 07
## 48 | 358
## 50 | 0000000000000000
#density
plot(density(Boston$medv))
#rug
rug(Boston$medv)ggplot(Boston, aes(x="var", y=black)) +
geom_boxplot() +
theme_bw() +
coord_flip() +
ggtitle("Boxplot variable black")ggplot(Boston, aes(x="var", y=crim)) +
geom_boxplot() +
theme_bw() +
coord_flip() +
ggtitle("Boxplot variable crim")ggplot(Boston, aes(x="var", y=tax)) +
geom_boxplot() +
theme_bw() +
coord_flip() +
ggtitle("Boxplot variable tax")ggplot(Boston, aes(x="var", y=zn)) +
geom_boxplot() +
theme_bw() +
coord_flip() +
ggtitle("Boxplot variable zn")Veamos ahora un poco de outliers con un dataset de películas que viene en el paquete ggplot2movies -antes formaba parte del paquete ggplot pero se hizo su propio paquete para bajar el overhead de bajar ggplot2-
library(ggplot2movies)
library(dplyr)
library(scales)
library(plotly)
data(movies)
glimpse(movies) ## Observations: 58,788
## Variables: 24
## $ title <chr> "$", "$1000 a Touchdown", "$21 a Day Once a Month"...
## $ year <int> 1971, 1939, 1941, 1996, 1975, 2000, 2002, 2002, 19...
## $ length <int> 121, 71, 7, 70, 71, 91, 93, 25, 97, 61, 99, 96, 10...
## $ budget <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA...
## $ rating <dbl> 6.4, 6.0, 8.2, 8.2, 3.4, 4.3, 5.3, 6.7, 6.6, 6.0, ...
## $ votes <int> 348, 20, 5, 6, 17, 45, 200, 24, 18, 51, 23, 53, 44...
## $ r1 <dbl> 4.5, 0.0, 0.0, 14.5, 24.5, 4.5, 4.5, 4.5, 4.5, 4.5...
## $ r2 <dbl> 4.5, 14.5, 0.0, 0.0, 4.5, 4.5, 0.0, 4.5, 4.5, 0.0,...
## $ r3 <dbl> 4.5, 4.5, 0.0, 0.0, 0.0, 4.5, 4.5, 4.5, 4.5, 4.5, ...
## $ r4 <dbl> 4.5, 24.5, 0.0, 0.0, 14.5, 14.5, 4.5, 4.5, 0.0, 4....
## $ r5 <dbl> 14.5, 14.5, 0.0, 0.0, 14.5, 14.5, 24.5, 4.5, 0.0, ...
## $ r6 <dbl> 24.5, 14.5, 24.5, 0.0, 4.5, 14.5, 24.5, 14.5, 0.0,...
## $ r7 <dbl> 24.5, 14.5, 0.0, 0.0, 0.0, 4.5, 14.5, 14.5, 34.5, ...
## $ r8 <dbl> 14.5, 4.5, 44.5, 0.0, 0.0, 4.5, 4.5, 14.5, 14.5, 4...
## $ r9 <dbl> 4.5, 4.5, 24.5, 34.5, 0.0, 14.5, 4.5, 4.5, 4.5, 4....
## $ r10 <dbl> 4.5, 14.5, 24.5, 45.5, 24.5, 14.5, 14.5, 14.5, 24....
## $ mpaa <chr> "", "", "", "", "", "", "R", "", "", "", "", "", "...
## $ Action <int> 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0,...
## $ Animation <int> 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
## $ Comedy <int> 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0,...
## $ Drama <int> 1, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1,...
## $ Documentary <int> 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
## $ Romance <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,...
## $ Short <int> 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0,...
## histograma
ggplot(movies, aes(x=length)) +
geom_histogram() +
scale_y_continuous(label=comma) +
scale_x_continuous(label=comma) +
theme_bw()¯\(º_o)/¯ chanclas… no dice mucho la gráfica… ¿o si? y además, ¿por qué sale más de 4,000 en longitud de la película? veamos qué hay ahí
movies %>%
filter(length > 2000) %>%
arrange(desc(length))Para encontrar estos outliers es más sencillo hacer diagramas de caja y brazos
p <-ggplot(movies, aes(x="var", y=length)) +
geom_boxplot() +
scale_x_discrete(breaks=NULL) +
scale_y_continuous(label=comma) +
theme_bw() +
coord_flip()
p #o con plotly para saber los numeros :P
ggplotly(p)Puedes obtener los outilers (valores) con la función boxplot
outliers_info <- boxplot(movies$length)summary(outliers_info) # las cosas que te devuelve el outlier ## Length Class Mode
## stats 5 integer numeric
## n 1 -none- numeric
## conf 2 -none- numeric
## out 10698 -none- numeric
## group 10698 -none- numeric
## names 1 -none- character
# cuantos
format(outliers_info$out %>% length(), big.mark=",")## [1] "10,698"
Veamos cómo se ve el histograma si quitamos estos valores atípicos
ggplot(movies, aes(x=length)) +
xlim(0, 180) +
geom_histogram(binwidth = 1) +
xlab("Duración de películas en minutos") +
theme_bw()que diferencia!
Ejercicio 1/Tarea 3 A entregar el lunes 9 de octubre 2017 en tu carpeta dentro de carpeta
alumno con el nombre ejercicio_eda_1 (entregar Rmd y html)
short que indica si una película es “corta”, ¿Qué gráfica puedes hacer para identificar el criterio que se ocupó para definir esta variable y cuáles están mal clasificadas?¿Qué podemos encontrar?
¿Cómo podríamos visualizar estas características?
¿Qué podemos encontrar?
Modelos y pruebas
¿Regresión lineal? … residuales
Ahora veamos la varaible rating que representa el promedio de calificaciones de IMDB y la variable votes que representa el número de personas que calificaron la película
ggplot(movies, aes(x=votes, y=rating)) +
geom_point() +
ylim(1,10) +
scale_x_continuous(label=comma) +
theme_bw() Ejercicio 2/Tarea 3 A entregar el lunes 9 de octubre 2017 en tu carpeta dentro de carpeta
alumno en el mismo archivo creado en el ejercicio 1
alpha-blending ¿Qué pasa con los outliers? ¿Diferentes valores funcionan mejor?Es posible estudiar posibles modelos (al igual que en el caso univariado) por ejemplo, ocupando el set de datos Cars93 en el paquete MASS:
data("Cars93")
ggplot(Cars93, aes(x=Weight, y=MPG.city)) +
geom_smooth(colour="green") +
ylim(0, 50) +
scale_x_continuous(label=comma) +
geom_point() +
theme_bw() Ejercicio 3/Tarea 3
MPG.city contra Horsepower? ¿Existe una relación lineal? ¿Cuáles son los outliers?También se podemos hacer una matriz de scatterplots -splom (como lo hicimos con los histogramas :)), para ello ocupamos el método ggpairs de la librería GGally en el dataset de precios de vivienda ade Boston.
library(GGally)
dplyr::select(Boston, -rad, -chas) %>%
ggpairs(title="Boston dataset", diag=list(continuous="density", axisLabels='none')) Ejercicio 4/Tarea 3
medv?crim -tasa de crímenes per cápita- tiene scatterplots con forma inusual, donde los valores altos de crim solo ocurren para un valor de la otra variable ¿Qué explicación le puedes dar?Para ver variables variables continuas se puede ocupar el parallel coordinate plot pcp, estos diagramas permiten dar un vista rápida a las distribuciones univariadas de varias variables a la vez: si son skew, si hay outliers, si hay gaps, etc.
Ahora utilizaremos el dataset iris para probar estas gráficas utilziando de la librería GGally el método ggparcoord
data(iris)
ggparcoord(iris, columns=1:4, groupColumn = "Species") Esta está más difícil de interpretar…
En un pcp cada línea es una observación del dataset, y cada atributo/variable del set es un punto en la gráfica.
Lo que uno observa en la gráfica depende del orden en el cuál se dibujan los ejes. Hay autores que sugieren intentar varias combinaciones o inclusive poner varias copias de los ejes. Quizá lo mejor sea tener una gráfica interactiva para tal efecto… lo veremos más adelante.
Este tipo de gráficas se puede usar para comparar modelos, series de tiempo, análisis de clusters, índices, etc. Esto lo discutiremos más adelante en el curso. Lo que se busca al ocupar esta gráfica es encontrar similitudes al comparar diferentes características del dataset.
Hay varias cosas que ajustar en estas gráficas para poder ser interpretadas: el orden de las variables y el escalamiento de los datos.
\[y_{ij}=\frac{x_{ij} - \min_i x_{ij}}{\max_i x_{ij} - \min_i x_{ij}}\]
Otra forma de escalar los datos es usando la desviación estándar o el rango intercuantil (IQR), por ejemplo:
\[z_{ij}=\frac{x_{ij} - \bar{x}_j}{sd(x_j)}\]
Este es el escalamiento por default que ocupa ggparcoord.
La escala de cada variable es propia a esa variable, es decir, no se deben comparar las alturas entre diferentes variables (diferentes puntos en el eje x)
iris1 <- iris
names(iris1) <- c(abbreviate(names(iris)[1:4]), "Species")
a1 <- ggparcoord(iris1, columns = 1:4,
alphaLines = 0.7,
groupColumn = "Species") +
ggtitle("a1")
a2 <- ggparcoord(iris1, columns = 1:4,
scale="uniminmax",
alphaLines=0.7,
groupColumn = "Species") +
ggtitle("a2")
a3 <- ggparcoord(iris1, columns = 1:4,
scale="globalminmax",
alphaLines=0.7,
groupColumn = "Species") +
ggtitle("a3")
a4 <- ggparcoord(iris1, columns = 1:4,
scale="center",
scaleSummary="median",
alphaLines=0.7,
groupColumn = "Species") +
ggtitle("a4")
gridExtra::grid.arrange(a1, a2, a3, a4) Ejercicio 5/Tarea 3
Boston realiza un pcp, intenta resaltar las características que haz observado en los ejercicios anteriores. Piensa cómo le hiciste…Cuando queremos comparar varias variables categóricas al mismo tiempo tenemos el problema de que haya muchas categorías por variable y la gran cantidad de posibles ordenamientoso de las variables. Por ejemplo, para \(J\) variables categóricas con \(c\) número de categorías, las variables pueden ser ordenadas de \(J!\) maneras diferentes y las categorías dentro de las variables de \(\Pi_{j=1}^J c_j!\), lo cual da un total de \(J!\Pi_{j=1}^Jc_j!\) ordenamientos, o sea un montón…
Los tipos de gráficas que podemos ocupar en estas situaciones son:
En esta gráfica las variables se dividen en variables explicativas y variable objetivo, esta última ocupa el eje vertical
library(vcd)
data(Titanic)
#variables a ocupar para hacer una tabla de contingencia, la variable tardet se pone al final
doubledecker(Survived ~ Sex, data=Titanic, gp=gpar(fill=c("grey90", "darkblue")))doubledecker(Survived ~ Class, data=Titanic, gp=gpar(fill=c("grey90", "darkblue")))Conforme más variables agregamos se vuelve más difícil la interpretación:
doubledecker(Survived ~ Sex + Class, data=Titanic, gp=gpar(fill=c("grey90", "darkblue")))Además, como mencionábamos, al ser una tabla de tipo mosaic la construcción de la gráfica depende del orden de las variables
doubledecker(Survived ~ Class + Sex, data=Titanic, gp=gpar(fill=c("grey90", "darkblue")))En este tipo de gráfica ocupamos rectángulos proporcionales a los conteos de las combinaciones que los rectángulos representan. Se dibujan partiendo con un rectángulo que representa todo el dataset, luego se toma la primer variable y se parte el eje horizontal en secciones proporcionales a los tamaños de las categorías.
titanic <- as.data.frame(Titanic)
par(mfrow=c(2,2), mar= c(4, 4, 0.1, 0.1))
mosaicplot(xtabs(Freq ~ Survived, data=titanic), main="")
mosaicplot(xtabs(Freq ~ Survived + Sex, data=titanic), main="")
mosaicplot(xtabs(Freq ~ Survived + Sex + Class, data=titanic), main="")
mosaicplot(xtabs(Freq ~ Survived + Sex + Class + Age, data=titanic), main="")De nuevo el orden de las variables determinará la apariencia (y tal vez el insight que puedes obtener)
Otra opción es utilizar pairs
pairs(xtabs(Freq ~ ., data=titanic))La diagonal de esta gráfica contiene gráficas de barra para las variables individuales y mosaicplots en los elementos que no están en la diagonal. Si hay muchas variables esta gráfica será muy difícil de leer.
Si existe una variable a explicar obvia (en nuestro caso Survived), quizá sea mejor tomar esto en cuenta en nuestro análisis:
ggplot(titanic, aes(Survived, Freq, fill=Sex)) +
geom_bar(stat = "identity") +
theme_bw() +
facet_grid(Class ~ Sex + Age) + theme(legend.position="none")Si queremos observar tablas de contingencia muy grandes o matrices de confusión, podemos usar las gráficas de tipo fluctile. En particular estas gráficas resaltan qué subgrupos aparecen más frecuentemente, o cuales combinaciones no aparecen en lo absoluto.
library(extracat)
#nota que requiere una tabla de entrada! no un data frame
fluctile(Titanic)Por otro lado, si solo nos interesa comparar las tasas, podemos usar la funcion rmb -relative multiple barchar- con el parámetro freq.trans="const":
rmb(formula = ~ Sex + Class + Age + Survived,
data=titanic,
cat.ord=2, spine=TRUE, freq.trans="const")En esta gráfica vemos las tasas de supervivencia (el color verde) por subgrupo. Esto sería muy difícil de apreciar con un mosaicplot normal.
Las rmb mezclan las gráficas de barras y los mosaicplots.
Para muchos de los análisis de datos requerimos que los datos estén en formato tidy
Tidy Data
Fuente: R for Data Science, Wickham and Grolemund, 2016
Es decir:
Fuente: Presentaciones de Hadley Wickham
Los siguientes ejemplos de dataset son típicos de algunas fuentes de datos, por ejemplo: INEGI (al menos en formatos al 2014)
| Lat | Long | Indicador | |
|---|---|---|---|
| Obs1 | # | # | # |
| lugar | indicador | |
|---|---|---|
| obs 1 | ||
| obs 2 |
| Fecha 1 | Fecha 2 | |
|---|---|---|
| lugar 1 | ||
| lugar 2 |
| Fecha 1 | Fecha 2 | … | |
|---|---|---|---|
| LUGAR 1 | |||
| Ind 1 | # | # | … |
| Ind 2 | # | # | … |
| LUGAR 2 | |||
| Ind 1 | # | # | … |
| Ind 2 | # | # | … |
O cosas más locas!
| Indicador 1 | Indicador 2 |
|---|
| Fecha 1 | Fecha 2 | Fecha 1 | Fecha 2 | |
|---|---|---|---|---|
| lugar 1 | Ind 1 | Ind 1 | Ind 2 | Ind 2 |
| lugar 1 | Ind 1 | Ind 1 | Ind 2 | Ind 2 |
Otro ejemplos:
Ejemplos:
library(tidyr)
messy <- data.frame(nombre=c("juan.perez.lopez","martha.lopez.benitez",
"jesus.ramirez.perez","jose.martinez.lopez",
"aurora.saldivar.salazar"),
genero_edad=c("m.35","f.23","m.30","m.25","f.33"),
time=c(1,3,4,5,6))
messyTendríamos que dejarlo tidy:
semi_messy <- messy %>% separate(col=nombre, into=c("nombre",
"apellido_paterno",
"apellido_materno"),
sep="\\.")
semi_messyclean <- semi_messy %>% separate(col=genero_edad, into=c("genero","edad"),
sep="\\.")
cleanOtro ejemplo: ¿Qué está mal?
messy <- data.frame(pais=c(rep("Afganistan",4),
rep("Brazil",4),
rep("China",4)),
year=c(rep(1999,2),rep(2000,2),
rep(1999,2),rep(2000,2),
rep(1999,2),rep(2000,2)),
llave=c("casos","poblacion","casos","poblacion",
"casos","poblacion","casos","poblacion",
"casos","poblacion","casos","poblacion"),
valor=c(75,1300000,134,1400000,
10000,100000000,12000,120000000,
56000,150000000,60000,170000000))
messyLo deberíamos arreglar con:
clean <- messy %>% spread(key=llave, value=valor, fill=NA)
cleanEl último
stocks <- data_frame(
time = as.Date('2009-01-01') + 0:9,
X = rnorm(10, 0, 1),
Y = rnorm(10, 0, 2),
Z = rnorm(10, 0, 4)
)
stocksstocks %>% gather(stock, price, -time)En estos casos de estudio nos vamos a encontrar con nuestro primer tipo de pipeline, en este caso en particular, este pipeline no es para ejecutar grandes volúmenes de datos o para ejecutar contínuamente, sino para poder reproducir el proceso de exploración y modelado de datos.
Antes de empezar te recomiendo ampliamente que utilices
packrat para la administración de paquetes en R packrat
En tu carpeta crea las carpetas german y algas dentro de ellas crea los archivos:
En estos archivos pondrás código para ejecutar los pipelines de los siguientes casos de estudio
Eres el científico de datos de un banco alemán, el banco tiene muchas pérdidas debido a malos créditos y quiere reducirlas. Te piden realizar esta tarea, indicando que quieren reducir la tasa de pérdidas en un 10%.
Neceistaras lo siguiente:
rm(list = ls())
instalar <- function(paquete) {
if (!require(paquete,character.only = TRUE, quietly = TRUE, warn.conflicts = FALSE)) {
install.packages(as.character(paquete), dependecies = TRUE, repos = "http://cran.us.r-project.org")
library(paquete, character.only = TRUE, quietly = TRUE, warn.conflicts = FALSE)
}
}
paquetes <- c('lubridate', 'magrittr', 'ggvis', 'dplyr', 'tidyr', 'readr', 'rvest',
'ggplot2', 'stringr', 'ggthemes', 'googleVis', 'shiny', 'tibble', 'vcd', 'vcdExtra',
'GGally', 'readODS', 'readxl', "RSQLite")
lapply(paquetes, instalar);
source("metadata.R")
source("utils.R")Usaremos para este ejemplo, los datos de crédito alemán (German data set). German Credit Data
german_url <- paste0('http://archive.ics.uci.edu/ml',
'/machine-learning-databases/statlog',
'/german/german.data')
german_data <- read_delim(german_url,
col_names=F,
delim=" ")Tenemos 1,000 observaciones y 21 variables. Veamos cómo están los datos:
head(german_data)¿Qué? (╯°□°)╯︵ ┻━┻
Ejercicio
load en utils.R en tu carpeta que descargue si y solo si no existe un archivo german.rds, si no existe descarga y guarda el archivo.?saveRDS, ?readRDS, ?file.existsLos nombres de las columnas fueron copiados a mano desde german.doc, los nombres se encuentran en el archivo metadata.R
source("utils.R")
german_colnames## [1] "Status of existing checking account"
## [2] "Duration in month"
## [3] "Credit history"
## [4] "Purpose"
## [5] "Credit amount"
## [6] "Savings account/bonds"
## [7] "Present employment since"
## [8] "Installment rate in percentage of disposable income"
## [9] "Personal status and sex"
## [10] "Other debtors / guarantors"
## [11] "Present residence since"
## [12] "Property"
## [13] "Age in years"
## [14] "Other installment plans"
## [15] "Housing"
## [16] "Number of existing credits at this bank"
## [17] "Job"
## [18] "Number of people being liable to provide maintenance for"
## [19] "Telephone"
## [20] "foreign worker"
## [21] "good_loan"
La variable de salida la definimos como categórica (factor en R)
colnames(german_data) <- german_colnames
german_data$good_loan <- as.factor(
ifelse(
german_data$good_loan == 1,
'GoodLoan',
'BadLoan'
)
)Crea una función german_decode en un archivo utils.R dentro de tu carpeta, esta función debe de utilizar german_codes (en el archivo metadata.R) para
decodificar los elementos de todas las columnas (por ejemplo A201 -> yes)
Utiliza dplyr para decodificar todas las columnas de german_data
TIP: verifica el uso de
left_join, rbind y cbind
german_data <- german_data %>%
mutate_all(funs(german_decode))
german_dataEn este momento deberás de tener archivos 00-load.R, 01-prepare.R, 02-clean.R, metadata.R y un archivo utils.R dentro de german.
Además deberías de tener un archivo german.rds.
Ejercicio
¿Hay algo raro con los datos de préstamo?
¿Cuál crees que debería ser la distribución del resultado del préstamo Good_Loan respecto a Credit history?
Grafícalo y comenta tus resultados.
Si lo vas a hacer con ggplot2 usa este cheatsheet o estos ejemplos
Tarea 4 Entrega lunes 16 de octubre 23:59:59 CST en tu carpeta alumnos/german
utils.R, 00-load.R, 01-prepare.R, 02-clean.R y run.R. \(\rightarrow\) Si tuvo sentido para ti poner las funciones en util y nada más … entonces solo sube tu util.R -te recomiendo que aunque hayas creado las funciones ahí de todas maneras generes el archivo 00 y 01 por claridad y sanidad mental tanto tuya como la de tu equipo :)-german si no existe y guardárla como rds, o si ya existe, cargarla con readRDSGood_Loan respecto a Credit history? Grafícalo y comenta tus resultados \(\rightarrow\) checa los tips de como gráficarlo con ggplot2 -te van a salir cosas raras=feas (visualmente)- pero no te estreses, las arreglaremos la siguiente clase ╭(◔ ◡ ◔)/Ejercicio
Fue terrible poder hacer la gráfica con ggplot2 utilizando los nombres de columnas que pusimos (german_colnames).
Modifica el archivo donde tengas german_colnames (puede ser utils.R o metadata.R) y sustituye (usando quizá stringr o grep) los ' ' y '/' por '_' (ve la guía de estilo) y pasa todo a minúsculas.
Ejecuta todo de nuevo (¡la ventaja de ser reproducible!)
colnames(german_data) <- german_clean_colnames(german_colnames)
colnames(german_data)## [1] "status_of_existing_checking_account"
## [2] "duration_in_month"
## [3] "credit_history"
## [4] "purpose"
## [5] "credit_amount"
## [6] "savings_account_bonds"
## [7] "present_employment_since"
## [8] "installment_rate_in_percentage_of_disposable_income"
## [9] "personal_status_and_sex"
## [10] "other_debtors___guarantors"
## [11] "present_residence_since"
## [12] "property"
## [13] "age_in_years"
## [14] "other_installment_plans"
## [15] "housing"
## [16] "number_of_existing_credits_at_this_bank"
## [17] "job"
## [18] "number_of_people_being_liable_to_provide_maintenance_for"
## [19] "telephone"
## [20] "foreign_worker"
## [21] "good_loan"
bar charts (Adolfo de Unánue)german_data %>%
group_by(credit_history) %>%
dplyr::summarise(count = n()) %>%
arrange(desc(count)) %>%
ggplot(.) +
geom_bar(aes(x=reorder(credit_history, count), y = count), stat="identity", fill="gray") +
coord_flip() +
theme_hc() +
ylab('casos') +
xlab('Historial de crédito')Yo me lo sé pasando a factores los nombres y ordenándo los niveles como necesites presentarlos -salida de una arrange-
plot_sorted <- german_data %>% group_by(credit_history) %>%
summarise(count=n()) %>%
arrange(count) #como haremos coord flip necesitamos ordenarlas de manera inversa a como queremos que aparezcan en el coord_flip
plot_sorted$credit_history <- factor(plot_sorted$credit_history,
levels=plot_sorted$credit_history)
ggplot(plot_sorted, aes(x=credit_history, y=count), fill="gray") +
geom_bar(stat="identity") +
coord_flip() +
theme_hc() +
ylab("casos") +
xlab("Historial de crédito")El nombre de la columna no significa lo que tu crees que significa
El significado de la columna cambia con el paso del tiempo o la metodología para medir esa variable.
Mucha / muy poca resolución
missing no son realmente faltantes (NAs), si no que significan algo
csv de seguro a alguien ya le pareció chistoso ponerle comas dentro de los valores de las columnas
tsv, psv, etc.summaryUn uso del summary() es detectar problemas en los datos.
Ejercicio
Revisa german_data con summary(), reporta alguna anomalía.
summary(german_data)## status_of_existing_checking_account duration_in_month credit_history
## Length:1000 Min. : 4.0 Length:1000
## Class :character 1st Qu.:12.0 Class :character
## Mode :character Median :18.0 Mode :character
## Mean :20.9
## 3rd Qu.:24.0
## Max. :72.0
## purpose credit_amount savings_account_bonds
## Length:1000 Min. : 250 Length:1000
## Class :character 1st Qu.: 1366 Class :character
## Mode :character Median : 2320 Mode :character
## Mean : 3271
## 3rd Qu.: 3972
## Max. :18424
## present_employment_since
## Length:1000
## Class :character
## Mode :character
##
##
##
## installment_rate_in_percentage_of_disposable_income
## Min. :1.000
## 1st Qu.:2.000
## Median :3.000
## Mean :2.973
## 3rd Qu.:4.000
## Max. :4.000
## personal_status_and_sex other_debtors___guarantors
## Length:1000 Length:1000
## Class :character Class :character
## Mode :character Mode :character
##
##
##
## present_residence_since property age_in_years
## Min. :1.000 Length:1000 Min. :19.00
## 1st Qu.:2.000 Class :character 1st Qu.:27.00
## Median :3.000 Mode :character Median :33.00
## Mean :2.845 Mean :35.55
## 3rd Qu.:4.000 3rd Qu.:42.00
## Max. :4.000 Max. :75.00
## other_installment_plans housing
## Length:1000 Length:1000
## Class :character Class :character
## Mode :character Mode :character
##
##
##
## number_of_existing_credits_at_this_bank job
## Min. :1.000 Length:1000
## 1st Qu.:1.000 Class :character
## Median :1.000 Mode :character
## Mean :1.407
## 3rd Qu.:2.000
## Max. :4.000
## number_of_people_being_liable_to_provide_maintenance_for
## Min. :1.000
## 1st Qu.:1.000
## Median :1.000
## Mean :1.155
## 3rd Qu.:1.000
## Max. :2.000
## telephone foreign_worker good_loan
## Length:1000 Length:1000 Length:1000
## Class :character Class :character Class :character
## Mode :character Mode :character Mode :character
##
##
##
Ejercicio
Asegurar que el dataset esté en forma tidy, si no lo está haz que esté en formato tidy (puede quedar en prepare/utils) Guarda esto en german-tidy.rds
Como el dinero no alcanza, tomas otro trabajo rápido para una ONG. Quieren predecir la concentración de algas en ríos de la región. Tomaron datos durante un año.
Cada observación es el efecto de agregar varias muestras de agua recolectadas en el mismo río por un periodo de 3 meses en la misma estación del año.
Los datos provienen de Coil 1999 Competition Data sobre contaminación de ríos. La explicación de los datos se puede ver aquí
algas_url <- 'https://archive.ics.uci.edu/ml/machine-learning-databases/coil-mld/analysis.data'
algas <- read_csv(algas_url,
col_names = algas_colnames,
na = 'XXXXXXX')## Parsed with column specification:
## cols(
## season = col_character(),
## river_size = col_character(),
## fluid_velocity = col_character(),
## max_PH = col_double(),
## min_O2 = col_double(),
## Cl = col_double(),
## NO3 = col_character(),
## NH4 = col_double(),
## oPO4 = col_double(),
## PO4 = col_double(),
## Chla = col_double(),
## a1 = col_double(),
## a2 = col_double(),
## a3 = col_double(),
## a4 = col_double(),
## a5 = col_double(),
## a6 = col_double(),
## a7 = col_double()
## )
## Warning in rbind(names(probs), probs_f): number of columns of result is not
## a multiple of vector length (arg 1)
## Warning: 17 parsing failures.
## row # A tibble: 5 x 5 col row col expected actual expected <int> <chr> <chr> <chr> actual 1 20 <NA> 18 columns 17 columns file 2 21 <NA> 18 columns 17 columns row 3 34 <NA> 18 columns 17 columns col 4 35 <NA> 18 columns 17 columns expected 5 36 <NA> 18 columns 17 columns actual # ... with 1 more variables: file <chr>
## ... ................. ... ................................... ........ ................................... ...... ................................... .... ................................... ... ................................... ... ................................... ........ ................................... ...... .......................................
## See problems(...) for more details.
Ejercicio
german.data con algassummary(), reporta alguna anomalía.summary(algas)## season river_size fluid_velocity max_PH
## Length:200 Length:200 Length:200 Min. :5.600
## Class :character Class :character Class :character 1st Qu.:7.700
## Mode :character Mode :character Mode :character Median :8.060
## Mean :8.012
## 3rd Qu.:8.400
## Max. :9.700
## NA's :1
## min_O2 Cl NO3 NH4
## Min. : 1.500 Min. : 0.222 Length:200 Min. : 5.00
## 1st Qu.: 7.725 1st Qu.: 10.981 Class :character 1st Qu.: 35.62
## Median : 9.800 Median : 32.730 Mode :character Median : 99.67
## Mean : 9.118 Mean : 43.636 Mean :154.45
## 3rd Qu.:10.800 3rd Qu.: 57.824 3rd Qu.:203.73
## Max. :13.400 Max. :391.500 Max. :931.83
## NA's :2 NA's :10 NA's :2
## oPO4 PO4 Chla a1
## Min. : 1.00 Min. : 0.90 Min. : 0.00 Min. : 0.000
## 1st Qu.: 16.00 1st Qu.: 19.39 1st Qu.: 2.00 1st Qu.: 1.475
## Median : 41.40 Median : 84.50 Median : 5.20 Median : 7.400
## Mean : 83.33 Mean :111.55 Mean : 13.54 Mean :16.863
## 3rd Qu.:102.25 3rd Qu.:182.16 3rd Qu.: 18.30 3rd Qu.:24.075
## Max. :771.60 Max. :558.75 Max. :110.46 Max. :89.800
## NA's :2 NA's :2 NA's :12
## a2 a3 a4 a5
## Min. : 0.000 Min. : 0.000 Min. : 0.000 Min. : 0.00
## 1st Qu.: 0.000 1st Qu.: 0.000 1st Qu.: 0.000 1st Qu.: 0.00
## Median : 2.100 Median : 1.750 Median : 0.000 Median : 1.90
## Mean : 6.934 Mean : 4.729 Mean : 1.885 Mean : 5.63
## 3rd Qu.: 9.075 3rd Qu.: 6.150 3rd Qu.: 2.225 3rd Qu.: 7.70
## Max. :72.600 Max. :44.600 Max. :35.600 Max. :77.60
##
## a6 a7
## Min. : 0.000 Min. : 0.000
## 1st Qu.: 0.000 1st Qu.: 0.000
## Median : 0.000 Median : 0.000
## Mean : 5.199 Mean : 2.506
## 3rd Qu.: 6.725 3rd Qu.: 2.400
## Max. :52.500 Max. :31.600
## NA's :17
¿Por qué la columna NO3 no es numérica?
problems(algas)El problema lo podemos observar, por ejemplo, en la observación 20
algas[20,]Otra cosa interesante a notar, es que hay justo 5 casos de a7 con NAs. Al parecer el error en la columna de NO3 se está “comiendo” a la columna a7.
algas[problems(algas)$row,]La columna de NO3 está capturada a 5 decimales. Parece lógica la suposición de dividir esa columna a los 5 decimales.
Podemos limpiar esta columna haciendo lo siguiente
problematic_rows <- problems(algas)$row
algas[problematic_rows,] <- algas %>%
slice(problematic_rows) %>%
unite(col="all", -seq(1:6), sep = "/", remove=TRUE) %>%
extract(all, into=c("NO3", "NH4", "resto"),
regex="([0-9]*.[0-9]{5})([0-9]*.[0-9]*)/(.*)/NA", remove=TRUE) %>%
separate(resto, into=names(algas)[9:18], sep="/", remove=TRUE)
algas[19:20,]¿Qué pasó aquí?
algas %>%
slice(problematic_rows) %>% head()
algas %>%
slice(problematic_rows) %>%
unite(col="all", -seq(1:6), sep="/", remove=T)
algas %>%
slice(problematic_rows) %>%
unite(col="all", -seq(1:6), sep="/", remove=T) %>%
extract(all, into=c("NO3", "NH4", "resto"),
regex="([0-9]*.[0-9]{5})([0-9]*.[0-9]*)/(.*)/NA", remove=T)algas <- readr::type_convert(algas)## Parsed with column specification:
## cols(
## season = col_character(),
## river_size = col_character(),
## fluid_velocity = col_character(),
## NO3 = col_double(),
## NH4 = col_double(),
## oPO4 = col_double(),
## PO4 = col_double(),
## Chla = col_double(),
## a1 = col_double(),
## a2 = col_double(),
## a3 = col_double(),
## a4 = col_double(),
## a5 = col_double(),
## a6 = col_double(),
## a7 = col_double()
## )
algasalgas <- algas %>%
mutate_all(funs(algas_clean))
algasRevisando el resumen estadístico con summary:
summary(algas)## season river_size fluid_velocity max_PH
## Length:200 Length:200 Length:200 Min. :5.600
## Class :character Class :character Class :character 1st Qu.:7.700
## Mode :character Mode :character Mode :character Median :8.060
## Mean :8.012
## 3rd Qu.:8.400
## Max. :9.700
## NA's :1
## min_O2 Cl NO3 NH4
## Min. : 1.500 Min. : 0.222 Min. : 0.050 Min. : 5.00
## 1st Qu.: 7.725 1st Qu.: 10.981 1st Qu.: 1.296 1st Qu.: 38.33
## Median : 9.800 Median : 32.730 Median : 2.675 Median : 103.17
## Mean : 9.118 Mean : 43.636 Mean : 3.282 Mean : 501.30
## 3rd Qu.:10.800 3rd Qu.: 57.824 3rd Qu.: 4.446 3rd Qu.: 226.95
## Max. :13.400 Max. :391.500 Max. :45.650 Max. :24064.00
## NA's :2 NA's :10 NA's :2 NA's :2
## oPO4 PO4 Chla a1
## Min. : 1.00 Min. : 1.00 Min. : 0.200 Min. : 0.00
## 1st Qu.: 15.70 1st Qu.: 41.38 1st Qu.: 2.000 1st Qu.: 1.50
## Median : 40.15 Median :103.29 Median : 5.475 Median : 6.95
## Mean : 73.59 Mean :137.88 Mean : 13.971 Mean :16.92
## 3rd Qu.: 99.33 3rd Qu.:213.75 3rd Qu.: 18.308 3rd Qu.:24.80
## Max. :564.60 Max. :771.60 Max. :110.456 Max. :89.80
## NA's :2 NA's :2 NA's :12
## a2 a3 a4 a5
## Min. : 0.000 Min. : 0.000 Min. : 0.000 Min. : 0.000
## 1st Qu.: 0.000 1st Qu.: 0.000 1st Qu.: 0.000 1st Qu.: 0.000
## Median : 3.000 Median : 1.550 Median : 0.000 Median : 1.900
## Mean : 7.458 Mean : 4.309 Mean : 1.992 Mean : 5.064
## 3rd Qu.:11.375 3rd Qu.: 4.925 3rd Qu.: 2.400 3rd Qu.: 7.500
## Max. :72.600 Max. :42.800 Max. :44.600 Max. :44.400
##
## a6 a7
## Min. : 0.000 Min. : 0.000
## 1st Qu.: 0.000 1st Qu.: 0.000
## Median : 0.000 Median : 1.000
## Mean : 5.964 Mean : 2.495
## 3rd Qu.: 6.925 3rd Qu.: 2.400
## Max. :77.600 Max. :31.600
##
Los tipos de datos:
glimpse(algas)## Observations: 200
## Variables: 18
## $ season <chr> "winter", "spring", "autumn", "spring", "autumn...
## $ river_size <chr> "small_", "small_", "small_", "small_", "small_...
## $ fluid_velocity <chr> "medium", "medium", "medium", "medium", "medium...
## $ max_PH <dbl> 8.00, 8.35, 8.10, 8.07, 8.06, 8.25, 8.15, 8.05,...
## $ min_O2 <dbl> 9.8, 8.0, 11.4, 4.8, 9.0, 13.1, 10.3, 10.6, 3.4...
## $ Cl <dbl> 60.800, 57.750, 40.020, 77.364, 55.350, 65.750,...
## $ NO3 <dbl> 6.238, 1.288, 5.330, 2.302, 10.416, 9.248, 1.53...
## $ NH4 <dbl> 578.000, 370.000, 346.667, 98.182, 233.700, 430...
## $ oPO4 <dbl> 105.000, 428.750, 125.667, 61.182, 58.222, 18.2...
## $ PO4 <dbl> 170.000, 558.750, 187.057, 138.700, 97.580, 56....
## $ Chla <dbl> 50.000, 1.300, 15.600, 1.400, 10.500, 28.400, 3...
## $ a1 <dbl> 0.0, 1.4, 3.3, 3.1, 9.2, 15.1, 2.4, 18.2, 25.4,...
## $ a2 <dbl> 0.0, 7.6, 53.6, 41.0, 2.9, 14.6, 1.2, 1.6, 5.4,...
## $ a3 <dbl> 0.0, 4.8, 1.9, 18.9, 7.5, 1.4, 3.2, 0.0, 2.5, 0...
## $ a4 <dbl> 0.0, 1.9, 0.0, 0.0, 0.0, 0.0, 3.9, 0.0, 0.0, 2....
## $ a5 <dbl> 34.2, 6.7, 0.0, 1.4, 7.5, 22.5, 5.8, 5.5, 0.0, ...
## $ a6 <dbl> 8.3, 0.0, 0.0, 0.0, 4.1, 12.6, 6.8, 8.7, 0.0, 0...
## $ a7 <dbl> 0.0, 2.1, 9.7, 1.4, 1.0, 2.9, 0.0, 0.0, 0.0, 1....
bimodal o multimodal quizá haya varias poblaciones en lugar de una y será mejor modelar por separado.Q-Qboxplot.density plots, en esta gráfica es más importante la forma que los valores actuales del eje vertical.skewed) y es no negativa es bueno representarla en log10.summary(), aunque algunos las prefieren.
Scatter plot entre dos variables numéricas, calcular la correlación de Pearson en un conjunto sano de los datos, visualizar la curva que mejor representa los datos.Stacked bar charts para dos variables categóricas.
filled bar chart. En este caso se recomienda agregar un rug para tener una idea de la cantidad de individuos.facets.boxplot (en su versión de violin o jitter).Tarea 5 Ejercicio. Rmd y html en el git dentro de tu carpeta con el nombre tarea_5_eda. Se entrega máximo el 23 de octubre 2017 23:59:59 CST (-0.5 por cada día de retraso). Enjoy! ╭(◔ ◡ ◔)/
data.frame (en realidad es un tibble). Esta función debe de recibir dos parámetros, uno que indique si genera todas las combinaciones de dos variables o recibe una lista de variables en las cuales generar las combinaciones.utils.R.03-eda.R en ambas carpetas: algas y german.NAsNAs o reemplazar (imputar) los NAs con valores razonables -puedes ocupar modelos para imputar los NA’s-NOTA: Todas estás recomendaciones aplican igual para outliers
Es importante recordar que en R la operación x == NA nunca regresa TRUE, siempre hay que utilizar las funciones is.na(), is.nan() e is.infinite().
El método complete.cases identifica los renglones (individuos) del data.frame que no tienen ningún NA en sus columnas (variables).
sum y mean con is.na para obtener el total por columna de faltantes y el porcentaje.
Aunque más adelante veremos técnicas más poderosas, vale la pena mencionar el ejemplo mostrado en R in action, cap. 15.
La técnica nos permite determinar si los faltantes en una variable están correlacionados con otra.
x <- as.data.frame(abs(is.na(algas))) # df es un data.frame
head(x)# Extrae las variables que tienen algunas celdas con NAs
y <- x[which(sapply(x, sd) > 0)]
# Da la correación, un valor alto positivo significa que desaparecen juntas.
cor(y) ## max_PH min_O2 Cl NO3 NH4
## max_PH 1.000000000 -0.007124524 -0.01626285 -0.007124524 -0.007124524
## min_O2 -0.007124524 1.000000000 0.20751434 0.494949495 0.494949495
## Cl -0.016262850 0.207514339 1.00000000 0.438085827 0.438085827
## NO3 -0.007124524 0.494949495 0.43808583 1.000000000 1.000000000
## NH4 -0.007124524 0.494949495 0.43808583 1.000000000 1.000000000
## oPO4 -0.007124524 0.494949495 0.43808583 1.000000000 1.000000000
## PO4 -0.007124524 -0.010101010 0.20751434 0.494949495 0.494949495
## Chla -0.017909570 0.186206796 0.81145218 0.397805428 0.397805428
## oPO4 PO4 Chla
## max_PH -0.007124524 -0.007124524 -0.01790957
## min_O2 0.494949495 -0.010101010 0.18620680
## Cl 0.438085827 0.207514339 0.81145218
## NO3 1.000000000 0.494949495 0.39780543
## NH4 1.000000000 0.494949495 0.39780543
## oPO4 1.000000000 0.494949495 0.39780543
## PO4 0.494949495 1.000000000 0.18620680
## Chla 0.397805428 0.186206796 1.00000000
Tarea 6. Ejercicio 1
Las variables con más faltantes son Promedio de Cloruro Cl (10) y Promedio de Clorofila Chla (12).
summary(algas[-grep(colnames(algas),pattern = "^a[1-9]")]) # Nota el uso del grep## season river_size fluid_velocity max_PH
## Length:200 Length:200 Length:200 Min. :5.600
## Class :character Class :character Class :character 1st Qu.:7.700
## Mode :character Mode :character Mode :character Median :8.060
## Mean :8.012
## 3rd Qu.:8.400
## Max. :9.700
## NA's :1
## min_O2 Cl NO3 NH4
## Min. : 1.500 Min. : 0.222 Min. : 0.050 Min. : 5.00
## 1st Qu.: 7.725 1st Qu.: 10.981 1st Qu.: 1.296 1st Qu.: 38.33
## Median : 9.800 Median : 32.730 Median : 2.675 Median : 103.17
## Mean : 9.118 Mean : 43.636 Mean : 3.282 Mean : 501.30
## 3rd Qu.:10.800 3rd Qu.: 57.824 3rd Qu.: 4.446 3rd Qu.: 226.95
## Max. :13.400 Max. :391.500 Max. :45.650 Max. :24064.00
## NA's :2 NA's :10 NA's :2 NA's :2
## oPO4 PO4 Chla
## Min. : 1.00 Min. : 1.00 Min. : 0.200
## 1st Qu.: 15.70 1st Qu.: 41.38 1st Qu.: 2.000
## Median : 40.15 Median :103.29 Median : 5.475
## Mean : 73.59 Mean :137.88 Mean : 13.971
## 3rd Qu.: 99.33 3rd Qu.:213.75 3rd Qu.: 18.308
## Max. :564.60 Max. :771.60 Max. :110.456
## NA's :2 NA's :2 NA's :12
También se puede hacer con dplyr
algas %>%
select(-starts_with("a")) %>%
summary() Antes de removerlas es recomendable verlos, guardarlos y contarlos:
nrow(algas[!complete.cases(algas),])## [1] 16
Hay 16 observaciones en las cuales tienen NAs
algas_con_NAs <- algas[!complete.cases(algas),]Siempre es bueno guardarlas si se piensan eliminar del dataset, ¿por qué?
Las observaciones con NAsson las siguientes (usaremos la función print() para explorar)
algas_con_NAs[c('max_PH', 'min_O2', 'Cl', 'NO3', 'NH4', 'oPO4', 'PO4', 'Chla')] %>%
print(n = 33)## # A tibble: 16 x 8
## max_PH min_O2 Cl NO3 NH4 oPO4 PO4 Chla
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 6.80 11.1 9.000 0.630 20 4.000 NA 2.70
## 2 8.00 NA 1.450 0.810 10 2.500 3.000 0.30
## 3 NA 12.6 9.000 0.230 10 5.000 6.000 1.10
## 4 6.60 10.8 NA 3.245 10 1.000 6.500 NA
## 5 5.60 11.8 NA 2.220 5 1.000 1.000 NA
## 6 5.70 10.8 NA 2.550 10 1.000 4.000 NA
## 7 6.60 9.5 NA 1.320 20 1.000 6.000 NA
## 8 6.60 10.8 NA 2.640 10 2.000 11.000 NA
## 9 6.60 11.3 NA 4.170 10 1.000 6.000 NA
## 10 6.50 10.4 NA 5.970 10 2.000 14.000 NA
## 11 6.40 NA NA NA NA NA 14.000 NA
## 12 7.83 11.7 4.083 1.328 18 3.333 6.667 NA
## 13 9.70 10.8 0.222 0.406 10 22.444 10.111 NA
## 14 9.00 5.8 NA 0.900 142 102.000 186.000 68.05
## 15 8.00 10.9 9.055 0.825 40 21.083 56.091 NA
## 16 8.00 7.6 NA NA NA NA NA NA
de los cuales, hay dos renglones que tienen más del 50% (6) de las variables independientes nulas.
Aunque remover las observaciones con NAs NO sea la estrategia, quitar las observaciones con muchas columnas vacías, puede ser recomendable.
En los casos en los que no es posible hacer una explorarción visual, se puede utilizar el siguiente código
# ¿Cuántos NAs hay por observación?
apply(algas, 1, function(x) sum(is.na(x)))## [1] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0
## [36] 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 2 2 2 2 2 2 2 6 1 0 0 0 0 0 0 0
## [71] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## [106] 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## [141] 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0
## [176] 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0
Si queremos ver las observaciones:
algas[apply(algas, 1, function(x) sum(is.na(x))) > 2,]Lo cual confirma nuestra exploración visual.
Si eliminar las observaciones con NAs va a ser el camino que vamos a tomar, habrá que hacerlo de manera reproducible, lo que sigue es el código de la función `indices_con_NAs’
indices_con_NAs## function (data, porcentaje = 0.2)
## {
## n <- if (porcentaje < 1) {
## as.integer(porcentaje * ncol(data))
## }
## else {
## stop("Debes de introducir el porcentaje de columnas con NAs.")
## }
## indices <- which(apply(data, 1, function(x) sum(is.na(x))) >
## n)
## if (!length(indices)) {
## warning("No hay observaciones con tantos NAs\\n (la respuesta de la función es vacía),\\n no se recomienda indexar el data.frame con esto")
## }
## indices
## }
indices_con_NAs(algas, 0.2)## [1] 62 199
indices_con_NAs(algas, 0.8)## Warning in indices_con_NAs(algas, 0.8): No hay observaciones con tantos NAs
## (la respuesta de la función es vacía),
## no se recomienda indexar el data.frame con esto
## integer(0)
# Si queremos remover las que tengan más del 20% de NAs...
algas <- algas[-indices_con_NAs(algas, 0.2),]
dim(algas)Si la variable es categórica (factor), puedes crear una nueva variable y poner los NAs a un nuevo level, e.g. missing
dataset$cat_with_NAs_fix <- ifelse(is.na(dataset$cat_with_NAs),
"missing",
ifelse(dataset$ccat_with_NAs == TRUE,
# o el valor que sea
"level_1",
"level_2"))Sólo recuerda que es posible que el valor de NA signifique algo.
Esto también se puede hacer con variables numéricas, si primero las vuelves categóricas (i.e. binning)
Para variables distribuidas normalmente, esta opción es la mejor.
Pero para variables skewed o con outliers esta decisión puede ser desastrosa.
Por lo tanto, esta estrategia no se debe de utilizar salvo una exploración previa de las variables.
Tarea 6. Ejercicio 2
algas les puedes aplicar este procedimiento?german_data?A las variables que no se les puede aplicar, explica por qué no.
Esta decisión debe de ser reproducible, agrega a utils.R una función que impute en las variables con NAs el valor central (median si es numérica, moda si es categórica). La función debe de tener la siguiente firma:
imputar_valor_central <- function(data, colnames) {...}Calculando rápidamente la correlación
algas[,-c(1:3)] %>%
cor(use="complete.obs") %>%
symnum()## m_P m_O Cl NO NH o P Ch a1 a2 a3 a4 a5 a6 a7
## max_PH 1
## min_O2 1
## Cl 1
## NO3 1
## NH4 , 1
## oPO4 . . 1
## PO4 . . * 1
## Chla . 1
## a1 . . . 1
## a2 . . 1
## a3 1
## a4 . . . 1
## a5 1
## a6 . . . 1
## a7 1
## attr(,"legend")
## [1] 0 ' ' 0.3 '.' 0.6 ',' 0.8 '+' 0.9 '*' 0.95 'B' 1
Observamos que oPO4 y PO4 están altamente relacionadas (> 0.9).
ggplot(data=algas) +
aes(x=oPO4, y=PO4) +
geom_point(shape=1) + # Usamos una bolita para los puntos
geom_smooth(method=lm, se=FALSE) +
theme_hc() # Mostramos la linea de la regresión y no mostramos la región de confianzaalgas#algas <- algas[-indices_con_NAs(algas, 0.2),]
modelo <- lm(PO4 ~ oPO4, data=algas)
modelo##
## Call:
## lm(formula = PO4 ~ oPO4, data = algas)
##
## Coefficients:
## (Intercept) oPO4
## 42.897 1.293
Entonces la fórmula que relaciona el PO4 con oPO4 es
\[ PO4 = 42.8970464 + 1.2930609*oPO4 \]
Tarea 6. Ejercicio 3
NAs con el valor dado por la regresión lineal recién calculada (No automatices la regresión lineal) usando la siguiente firmaimputar_valor_lm <- function(var_independiente, modelo) { ... }NAs en alguna variable, hay una alta probabilidad de que esa variable tenga un valor similar al valor de esa variable en la otra observación.
\[ d(\vec{x}, \vec{y}) = \sqrt{\sum_{i=1}^p(\vec{x}_i - \vec{y}_i)} \]
\[ d(\vec{x}, \vec{y}) = \sqrt{\sum_{i=1}^p \delta_i(\vec{x}_i, \vec{y}_i)} \]
donde \(\delta(\vec{x}, \vec{y})\) es la delta de Kronecker ¿qué? (;-_-)
La dela de Kronecher identifica si dos variables nominales son iguales o no:
\[\delta = \begin{cases} 1 & \text{if } x=0 \\ 0 & \text{if } x>0 \end{cases}\]
Ejemplos:
\(\delta_{-1}^{sin(2017 \sqrt[6]{2})}=0\)
NA.El Promedio con peso de los valores de los vecinos, es otra opción. El peso se puede determinar de varias maneras, pero usar como kernel una función gaussiana.
\[ peso(d) = e^{-d} \]
donde \(d\) es la distancia de una observación a la que estamos considerando.
\[ \vec{x}_{normalizado} = \frac{\vec{x}_i - \bar{x}}{\sigma_{x}} \]
¿Por qué?
Tarea 6. Ejercicio 4
imputar_por_similitud <- function(data, num_vecinos) { ... }algas y german.NAs comparadas con este método?Paréntesis cultural, en minería de datos:
Normalizar/estandarizar es útil cuando las cantidades absolutas son menos importantes que las relativas.
log10() lo hará útil.log si el rango de tus datos cubre varios ordenes de magnitud.
signedLog10signedLog10 <- function(x) {
ifelse(abs(x) <= 1.0, sign(x)*log10(abs(x)))
}Tarea 6, Ejercicio 5
utils.R que se puedan reutilizar en un archivo toolset.R. Ajusta tus demás archivos de acuerdo a este cambio -si aplica-R notebook para cada dataset utilizando los archivos reproducibles y el archivo toolset.R. Incluye en estos notebook la estructura de los datos, GEDA transformaciones de los datos y observaciones pertinentes (como outliers, estructura de los datos faltantes). Explica los métodos de imputación que usaste (si fué necesario) y porqué los usaste.feature selectionTitanic
titanic_path <- '../data/Titanic/titanic.ods'
ds_names <- ods_sheets(titanic_path)
ds_names## [1] "1er Clase" "2da Clase" "3era Clase"
## [4] "Deck" "Engine" "Victualling"
## [7] "Restaurant" "Postal Clerk" "Guarantee Group"
## [10] "Ship's Orchesta" "Discharged crew"
Arreglemos los nombres de los data sets
clean_sheet_name <- function(sheet_name) {
str_replace_all(str_replace_all(string=sheet_name, pattern=" ", replace="_"), pattern="'", replace="") %>%
str_to_lower()
}
sapply(ds_names, clean_sheet_name)## 1er Clase 2da Clase 3era Clase Deck
## "1er_clase" "2da_clase" "3era_clase" "deck"
## Engine Victualling Restaurant Postal Clerk
## "engine" "victualling" "restaurant" "postal_clerk"
## Guarantee Group Ship's Orchesta Discharged crew
## "guarantee_group" "ships_orchesta" "discharged_crew"
Ahora obtenemos los datasets y los guardamos
save_sheet <- function(sheet_name) {
file_name <- paste0("../data/Titanic/", clean_sheet_name(sheet_name), ".rds")
saveRDS(object = read_ods(titanic_path, sheet = sheet_name), file = file_name)
}
lapply(ods_sheets(titanic_path), save_sheet)## [[1]]
## NULL
##
## [[2]]
## NULL
##
## [[3]]
## NULL
##
## [[4]]
## NULL
##
## [[5]]
## NULL
##
## [[6]]
## NULL
##
## [[7]]
## NULL
##
## [[8]]
## NULL
##
## [[9]]
## NULL
##
## [[10]]
## NULL
##
## [[11]]
## NULL
Cargar los datos:
rm(list=ls())
rds_files <- dir("../data/Titanic/", pattern = "*.rds", full.names = TRUE)
#lapply te devolverá las cosas en un lista... una lista de dataframes :)
ds <- lapply(rds_files, read_rds)
class(ds)## [1] "list"
class(ds[[1]])## [1] "data.frame"
#cuantos dataframes contiene esta lista?
length(ds)## [1] 11
#basename elimina todo el path del nombre excepto la última parte (se quedará con la extensión del archivo!), ?basename
names(ds) <- lapply(rds_files, basename)
names(ds)## [1] "1er_clase.rds" "2da_clase.rds" "3era_clase.rds"
## [4] "deck.rds" "discharged_crew.rds" "engine.rds"
## [7] "guarantee_group.rds" "postal_clerk.rds" "restaurant.rds"
## [10] "ships_orchesta.rds" "victualling.rds"
#veamos qué nombres tiene cada dataframe
lapply(ds, names)## $`1er_clase.rds`
## [1] "Name" "Age" "Class/Dept" "Ticket" "Fare"
## [6] "Group" "Ship" "Joined" "Job" "Boat"
## [11] "Body" "" " "
##
## $`2da_clase.rds`
## [1] "Name" "Age" "Class/Dept" "Ticket" "Fare"
## [6] "Group" "Ship" "Joined" "Job" "Boat"
## [11] "Body" "" " "
##
## $`3era_clase.rds`
## [1] "Name" "Age" "Class/Dept" "Ticket" "Fare"
## [6] "Group" "Ship" "Joined" "Job" "Boat"
## [11] "Body"
##
## $deck.rds
## [1] "Name" "Age" "Class/Dept" "Ticket" "Fare"
## [6] "Group" "Ship" "Joined" "Job" "Boat"
## [11] "Body"
##
## $discharged_crew.rds
## [1] "" ""
##
## $engine.rds
## [1] "Name" "Age" "Class/Dept" "Ticket" "Fare"
## [6] "Group" "Ship" "Joined" "Job" "Boat"
## [11] "Body"
##
## $guarantee_group.rds
## [1] "Name" "Age" "Class/Dept" "Ticket" "Fare"
## [6] "Group" "Ship" "Joined" "Job" "Boat"
## [11] "Body"
##
## $postal_clerk.rds
## [1] "Name" "Age" "Class/Dept" "Ticket" "Fare"
## [6] "Group" "Ship" "Joined" "Job" "Boat"
## [11] "Body" "" ""
##
## $restaurant.rds
## [1] "Name" "Age" "Class/Dept" "Ticket" "Fare"
## [6] "Group" "Ship" "Joined" "Job" "Boat"
## [11] "Body" "" " "
##
## $ships_orchesta.rds
## [1] "Name" "Age" "Class/Dept" "Ticket" "Fare"
## [6] "Group" "Ship" "Joined" "Job" "Boat"
## [11] "Body"
##
## $victualling.rds
## [1] "Name" "Age" "Class/Dept" "Ticket" "Fare"
## [6] "Group" "Ship" "Joined" "Job" "Boat"
## [11] "Body" "" " "
#si quisieramos obtener los conjuntos de nombres únicos
lapply(ds, names) %>% unique()## [[1]]
## [1] "Name" "Age" "Class/Dept" "Ticket" "Fare"
## [6] "Group" "Ship" "Joined" "Job" "Boat"
## [11] "Body" "" " "
##
## [[2]]
## [1] "Name" "Age" "Class/Dept" "Ticket" "Fare"
## [6] "Group" "Ship" "Joined" "Job" "Boat"
## [11] "Body"
##
## [[3]]
## [1] "" ""
##
## [[4]]
## [1] "Name" "Age" "Class/Dept" "Ticket" "Fare"
## [6] "Group" "Ship" "Joined" "Job" "Boat"
## [11] "Body" "" ""
Vemos que hay 4 tipos de listas de nombres diferentes:
Averigüemos más:
lapply(ds, head)## $`1er_clase.rds`
## Name Age Class/Dept Ticket Fare
## 1 ALLEN, Miss Elisabeth Walton 29 1st Class 24160 £211 6s 9d
## 2 ALLISON, Mr Hudson Joshua Creighton 30 1st Class 113781 £151 16s
## 3 ALLISON, Mrs Bessie Waldo 25 1st Class 113781 £151 16s
## 4 ALLISON, Miss Helen Loraine 2 1st Class 113781 £151 16s
## 5 ALLISON, Master Hudson Trevor 11m 1st Class 113781 £151 16s
## 6 ANDERSON, Mr Harry 47 1st Class 19952 £26 11s
## Group Ship Joined Job Boat Body
## 1 <NA> <NA> Southampton <NA> 2 NA <NA> <NA>
## 2 <NA> <NA> Southampton Businessman <NA> 135 <NA> <NA>
## 3 <NA> <NA> Southampton <NA> <NA> NA <NA> <NA>
## 4 <NA> <NA> Southampton <NA> <NA> NA <NA> <NA>
## 5 <NA> <NA> Southampton <NA> 11 NA <NA> <NA>
## 6 <NA> <NA> Southampton Stockbroker 3 NA <NA> <NA>
##
## $`2da_clase.rds`
## Name Age Class/Dept Ticket Fare Group Ship
## 1 ABELSON, Mr Samuel 30 2nd Class 3381 £24 <NA> <NA>
## 2 ABELSON, Mrs 28 2nd Class 3381 £24 <NA> <NA>
## 3 ALDWORTH, Mr Charles Augustus 30 2nd Class 248744 £13 Servant <NA>
## 4 ANDREW, Mr Edgar Samuel 17 2nd Class 231945 £11 10s <NA> <NA>
## 5 ANDREW, Mr Frank Thomas 25 2nd Class 34050 £10 10s <NA> <NA>
## 6 ANGLE, Mr William A. 32 2nd Class 226875 £26 <NA> <NA>
## Joined Job Boat Body
## 1 Cherbourg <NA> <NA> NA <NA> <NA>
## 2 Cherbourg <NA> 10 NA <NA> <NA>
## 3 Southampton Chauffeur <NA> NA <NA> <NA>
## 4 Southampton <NA> <NA> NA <NA> <NA>
## 5 Southampton Miner <NA> NA <NA> <NA>
## 6 Southampton <NA> <NA> NA <NA> <NA>
##
## $`3era_clase.rds`
## Name Age Class/Dept Ticket Fare Group Ship
## 1 ABBING, Mr Anthony 42 3rd Class 5547 £7 11s <NA> <NA>
## 2 ABBOTT, Mrs Rhoda Mary 'Rosa' 39 3rd Class CA2673 £20 5s <NA> <NA>
## 3 ABBOTT, Mr Rossmore Edward 16 3rd Class CA2673 £20 5s <NA> <NA>
## 4 ABBOTT, Mr Eugene Joseph 14 3rd Class CA2673 £20 5s <NA> <NA>
## 5 ABELSETH, Miss Karen Marie 16 3rd Class 348125 £7 13s <NA> <NA>
## 6 ABELSETH, Mr Olaus Jørgensen 25 3rd Class 348122 £7 13s <NA> <NA>
## Joined Job Boat Body
## 1 Southampton Blacksmith <NA> NA
## 2 Southampton <NA> A NA
## 3 Southampton Jeweller <NA> 190
## 4 Southampton Scholar <NA> NA
## 5 Southampton <NA> 16 NA
## 6 Southampton Farmer A NA
##
## $deck.rds
## Name Age Class/Dept Ticket Fare Group Ship
## 1 ANDERSON, Mr James 40 Deck <NA> <NA> <NA> <NA>
## 2 ARCHER, Mr Ernest Edward 36 Deck <NA> <NA> <NA> <NA>
## 3 BAILEY, Mr Henry Joseph 43 Deck <NA> <NA> <NA> <NA>
## 4 BOXHALL, Mr Joseph Groves 28 Deck <NA> <NA> <NA> <NA>
## 5 BRADLEY, Mr T. 29 Deck <NA> <NA> <NA> <NA>
## 6 BRICE, Mr Walter T 42 Deck <NA> <NA> <NA> <NA>
## Joined Job Boat Body
## 1 Southampton Able Seaman 3 NA
## 2 Southampton Able Seaman 16 NA
## 3 Southampton Master-at-arms 16 NA
## 4 Belfast 4th. Officer 2 NA
## 5 Southampton Able Seaman <NA> NA
## 6 Southampton Able Seaman 11 NA
##
## $discharged_crew.rds
##
## 1 <NA> <NA>
## 2 Blake, Mr C. [Trimmer - Failed to Join]
## 3 Bowman, Mr J. (?F.T.) [Assistant Cook - Failed to Join]
## 4 Brewer, Mr B. [Trimmer - Deserted]
## 5 Burrows, Mr W. [Fireman - Left by Consent]
## 6 Carter, Mr F. [Trimmer - Failed to join]
##
## $engine.rds
## Name Age Class/Dept Ticket Fare Group Ship
## 1 ABRAMS, Mr William 33 Engine <NA> <NA> <NA> <NA>
## 2 ADAMS, Mr R. 26 Engine <NA> <NA> <NA> <NA>
## 3 ALLEN, Mr Henry 32 Engine <NA> <NA> <NA> <NA>
## 4 ALLEN, Mr Ernest Frederick 24 Engine <NA> <NA> <NA> <NA>
## 5 ALLSOP, Mr Alfred Samuel 34 Engine <NA> <NA> <NA> <NA>
## 6 AVERY, Mr James Albert 22 Engine <NA> <NA> <NA> <NA>
## Joined Job Boat Body
## 1 Southampton Fireman / Stoker <NA> NA
## 2 Southampton Fireman / Stoker <NA> NA
## 3 Southampton Fireman / Stoker <NA> 145
## 4 Southampton Trimmer B NA
## 5 Belfast Electrician <NA> NA
## 6 Southampton Trimmer 15 NA
##
## $guarantee_group.rds
## Name Age Class/Dept Ticket Fare
## 1 ANDREWS, Mr Thomas 39 1st Class 112050 <NA>
## 2 CAMPBELL, Mr William Henry 21 2nd Class 239853 <NA>
## 3 CHISHOLM, Mr Roderick Robert Crispin 40 1st Class 112051 <NA>
## 4 CUNNINGHAM, Mr Alfred Fleming 21 2nd Class 239853 <NA>
## 5 FROST, Mr Anthony Wood 37 2nd Class 239854 <NA>
## 6 KNIGHT, Mr Robert J. 39 2nd Class 239855 <NA>
## Group Ship Joined Job Boat Body
## 1 H&W Guarantee Group <NA> Belfast Shipbuilder <NA> <NA>
## 2 H&W Guarantee Group <NA> Belfast <NA> <NA> <NA>
## 3 H&W Guarantee Group <NA> Belfast Draughtsman <NA> <NA>
## 4 H&W Guarantee Group <NA> Belfast Fitter <NA> <NA>
## 5 H&W Guarantee Group <NA> Belfast Fitter <NA> <NA>
## 6 H&W Guarantee Group <NA> Belfast Fitter <NA> <NA>
##
## $postal_clerk.rds
## Name Age Class/Dept Ticket Fare Group
## 1 GWYNN, Mr William Logan 37 Victualling <NA> <NA> Postal Clerk
## 2 MARCH, Mr John Starr 50 Victualling <NA> <NA> Postal Clerk
## 3 SMITH, Mr John Richard Jago 35 Victualling <NA> <NA> Postal Clerk
## 4 WILLIAMSON, Mr James Bertram 35 Victualling <NA> <NA> Postal Clerk
## 5 WOODY, Mr Oscar Scott 44 Victualling <NA> <NA> Postal Clerk
## 6 <NA> NA <NA> <NA> <NA> <NA>
## Ship Joined Job Boat Body
## 1 <NA> Southampton Postal Clerk <NA> NA <NA> <NA>
## 2 <NA> Southampton Postal Clerk <NA> 225 <NA> <NA>
## 3 <NA> Southampton Postal Clerk <NA> NA <NA> <NA>
## 4 <NA> Southampton Postal Clerk <NA> NA <NA> <NA>
## 5 <NA> Southampton Postal Clerk <NA> 167 <NA> <NA>
## 6 <NA> <NA> <NA> <NA> NA <NA> <NA>
##
## $restaurant.rds
## Name Age Class/Dept Ticket Fare Group Ship
## 1 ALLARIA, Sig. Battista Antonio 22 A la Carte <NA> <NA> <NA> <NA>
## 2 ASPESLAGH, Mr Georges 26 A la Carte <NA> <NA> <NA> <NA>
## 3 BANFI, Sig. Ugo 24 A la Carte <NA> <NA> <NA> <NA>
## 4 BASILICO, Sig. Giovanni 27 A la Carte <NA> <NA> <NA> <NA>
## 5 BAZZI, Sig. Narciso 33 A la Carte <NA> <NA> <NA> <NA>
## 6 BERNARDI, Sig. Battista 22 A la Carte <NA> <NA> <NA> <NA>
## Joined Job Boat Body
## 1 Southampton Assistant Waiter <NA> 221 <NA> <NA>
## 2 Southampton Assistant Plateman <NA> NA <NA> <NA>
## 3 Southampton Waiter <NA> NA <NA> <NA>
## 4 Southampton Waiter <NA> NA <NA> <NA>
## 5 Southampton Waiter <NA> NA <NA> <NA>
## 6 Southampton Assistant Waiter <NA> 215 <NA> <NA>
##
## $ships_orchesta.rds
## Name Age Class/Dept Ticket Fare Group
## 1 BRAILEY, Mr W. Theodore Ronald 24 2nd Class 250654 <NA> Musician
## 2 BRICOUX, Mr Roger Marie 20 2nd Class 250654 <NA> Musician
## 3 CLARKE, Mr John Frederick Preston 30 2nd Class 250654 <NA> Musician
## 4 HARTLEY, Mr Wallace Henry 33 2nd Class 250654 <NA> Musician
## 5 HUME, Mr John Law 21 2nd Class 250654 <NA> Musician
## 6 KRINS, Mr Georges Alexandre 23 2nd Class 250654 <NA> Musician
## Ship Joined Job Boat Body
## 1 <NA> Southampton Musician <NA> NA
## 2 <NA> Southampton Musician <NA> NA
## 3 <NA> Southampton Musician <NA> 202
## 4 <NA> Southampton Musician <NA> 224
## 5 <NA> Southampton Musician <NA> 193
## 6 <NA> Southampton Musician <NA> NA
##
## $victualling.rds
## Name Age Class/Dept Ticket Fare Group Ship
## 1 ABBOTT, Mr Ernest Owen 21 Victualling <NA> <NA> <NA> <NA>
## 2 AHIER, Mr Percy Snowden 20 Victualling <NA> <NA> <NA> <NA>
## 3 AKERMAN, Mr Albert 28 Victualling <NA> <NA> <NA> <NA>
## 4 AKERMAN, Mr Joseph Francis 35 Victualling <NA> <NA> <NA> <NA>
## 5 ALLAN, Mr Robert Spencer 36 Victualling <NA> <NA> <NA> <NA>
## 6 ALLEN, Mr George 26 Victualling <NA> <NA> <NA> <NA>
## Joined Job Boat Body
## 1 Southampton Lounge Pantry Steward <NA> NA <NA> <NA>
## 2 Southampton Saloon Steward <NA> NA <NA> <NA>
## 3 Southampton Steward <NA> NA <NA> <NA>
## 4 Southampton Assistant Pantryman Steward <NA> 205 <NA> <NA>
## 5 Southampton Bed Room Steward <NA> NA <NA> <NA>
## 6 Southampton Scullion <NA> NA <NA> <NA>
El data frame discharged_crew.rds tiene dos columnas que no tienen nombre y parecen contener el nombre del empleado, el oficio y la razón de su salida como empleado del barcof. Quitemos este data frame de nuestro set de datos
ds <- ds[-which(lapply(lapply(ds, names), length) == 2)]Obtengamos el número mínimo de nombres como base (los demás tienen columnas vacías)
num_cols <- lapply((lapply(ds, names)), length) %>% unlist() %>% min()
num_cols ## [1] 11
Pero… primero revisemos que los tipos de datos son los mismos -no vaya a ser-
lapply(ds, str)## 'data.frame': 324 obs. of 13 variables:
## $ Name : chr "ALLEN, Miss Elisabeth Walton" "ALLISON, Mr Hudson Joshua Creighton" "ALLISON, Mrs Bessie Waldo" "ALLISON, Miss Helen Loraine" ...
## $ Age : chr "29" "30" "25" "2" ...
## $ Class/Dept: chr "1st Class" "1st Class" "1st Class" "1st Class" ...
## $ Ticket : chr "24160" "113781" "113781" "113781" ...
## $ Fare : chr "£211 6s 9d" "£151 16s" "£151 16s" "£151 16s" ...
## $ Group : chr NA NA NA NA ...
## $ Ship : chr NA NA NA NA ...
## $ Joined : chr "Southampton" "Southampton" "Southampton" "Southampton" ...
## $ Job : chr NA "Businessman" NA NA ...
## $ Boat : chr "2" NA NA NA ...
## $ Body : int NA 135 NA NA NA NA NA NA NA 22 ...
## $ : chr NA NA NA NA ...
## $ : chr NA NA NA NA ...
## 'data.frame': 285 obs. of 13 variables:
## $ Name : chr "ABELSON, Mr Samuel" "ABELSON, Mrs" "ALDWORTH, Mr Charles Augustus" "ANDREW, Mr Edgar Samuel" ...
## $ Age : chr "30" "28" "30" "17" ...
## $ Class/Dept: chr "2nd Class" "2nd Class" "2nd Class" "2nd Class" ...
## $ Ticket : chr "3381" "3381" "248744" "231945" ...
## $ Fare : chr "£24" "£24" "£13" "£11 10s" ...
## $ Group : chr NA NA "Servant" NA ...
## $ Ship : chr NA NA NA NA ...
## $ Joined : chr "Cherbourg" "Cherbourg" "Southampton" "Southampton" ...
## $ Job : chr NA NA "Chauffeur" NA ...
## $ Boat : chr NA "10" NA NA ...
## $ Body : int NA NA NA NA NA NA NA NA NA NA ...
## $ : chr NA NA NA NA ...
## $ : chr NA NA NA NA ...
## 'data.frame': 708 obs. of 11 variables:
## $ Name : chr "ABBING, Mr Anthony" "ABBOTT, Mrs Rhoda Mary 'Rosa'" "ABBOTT, Mr Rossmore Edward" "ABBOTT, Mr Eugene Joseph" ...
## $ Age : chr "42" "39" "16" "14" ...
## $ Class/Dept: chr "3rd Class" "3rd Class" "3rd Class" "3rd Class" ...
## $ Ticket : chr "5547" "CA2673" "CA2673" "CA2673" ...
## $ Fare : chr "£7 11s" "£20 5s" "£20 5s" "£20 5s" ...
## $ Group : chr NA NA NA NA ...
## $ Ship : chr NA NA NA NA ...
## $ Joined : chr "Southampton" "Southampton" "Southampton" "Southampton" ...
## $ Job : chr "Blacksmith" NA "Jeweller" "Scholar" ...
## $ Boat : chr NA "A" NA NA ...
## $ Body : int NA NA 190 NA NA NA NA NA 72 103 ...
## 'data.frame': 66 obs. of 11 variables:
## $ Name : chr "ANDERSON, Mr James" "ARCHER, Mr Ernest Edward" "BAILEY, Mr Henry Joseph" "BOXHALL, Mr Joseph Groves" ...
## $ Age : int 40 36 43 28 29 42 42 27 34 31 ...
## $ Class/Dept: chr "Deck" "Deck" "Deck" "Deck" ...
## $ Ticket : chr NA NA NA NA ...
## $ Fare : chr NA NA NA NA ...
## $ Group : chr NA NA NA NA ...
## $ Ship : chr NA NA NA NA ...
## $ Joined : chr "Southampton" "Southampton" "Southampton" "Belfast" ...
## $ Job : chr "Able Seaman" "Able Seaman" "Master-at-arms" "4th. Officer" ...
## $ Boat : chr "3" "16" "16" "2" ...
## $ Body : int NA NA NA NA NA NA NA NA NA NA ...
## 'data.frame': 325 obs. of 11 variables:
## $ Name : chr "ABRAMS, Mr William" "ADAMS, Mr R." "ALLEN, Mr Henry" "ALLEN, Mr Ernest Frederick" ...
## $ Age : int 33 26 32 24 34 22 46 24 32 30 ...
## $ Class/Dept: chr "Engine" "Engine" "Engine" "Engine" ...
## $ Ticket : chr NA NA NA NA ...
## $ Fare : chr NA NA NA NA ...
## $ Group : chr NA NA NA NA ...
## $ Ship : chr NA NA NA NA ...
## $ Joined : chr "Southampton" "Southampton" "Southampton" "Southampton" ...
## $ Job : chr "Fireman / Stoker" "Fireman / Stoker" "Fireman / Stoker" "Trimmer" ...
## $ Boat : chr NA NA NA "B" ...
## $ Body : int NA NA 145 NA NA NA NA NA NA NA ...
## 'data.frame': 9 obs. of 11 variables:
## $ Name : chr "ANDREWS, Mr Thomas" "CAMPBELL, Mr William Henry" "CHISHOLM, Mr Roderick Robert Crispin" "CUNNINGHAM, Mr Alfred Fleming" ...
## $ Age : int 39 21 40 21 37 39 18 29 15
## $ Class/Dept: chr "1st Class" "2nd Class" "1st Class" "2nd Class" ...
## $ Ticket : int 112050 239853 112051 239853 239854 239855 239853 112052 239856
## $ Fare : chr NA NA NA NA ...
## $ Group : chr "H&W Guarantee Group" "H&W Guarantee Group" "H&W Guarantee Group" "H&W Guarantee Group" ...
## $ Ship : chr NA NA NA NA ...
## $ Joined : chr "Belfast" "Belfast" "Belfast" "Belfast" ...
## $ Job : chr "Shipbuilder" NA "Draughtsman" "Fitter" ...
## $ Boat : chr NA NA NA NA ...
## $ Body : chr NA NA NA NA ...
## 'data.frame': 15 obs. of 13 variables:
## $ Name : chr "GWYNN, Mr William Logan" "MARCH, Mr John Starr" "SMITH, Mr John Richard Jago" "WILLIAMSON, Mr James Bertram" ...
## $ Age : int 37 50 35 35 44 NA NA NA NA NA ...
## $ Class/Dept: chr "Victualling" "Victualling" "Victualling" "Victualling" ...
## $ Ticket : chr NA NA NA NA ...
## $ Fare : chr NA NA NA NA ...
## $ Group : chr "Postal Clerk" "Postal Clerk" "Postal Clerk" "Postal Clerk" ...
## $ Ship : chr NA NA NA NA ...
## $ Joined : chr "Southampton" "Southampton" "Southampton" "Southampton" ...
## $ Job : chr "Postal Clerk" "Postal Clerk" "Postal Clerk" "Postal Clerk" ...
## $ Boat : chr NA NA NA NA ...
## $ Body : int NA 225 NA NA 167 NA NA NA NA NA ...
## $ : chr NA NA NA NA ...
## $ : chr NA NA NA NA ...
## 'data.frame': 69 obs. of 13 variables:
## $ Name : chr "ALLARIA, Sig. Battista Antonio" "ASPESLAGH, Mr Georges" "BANFI, Sig. Ugo" "BASILICO, Sig. Giovanni" ...
## $ Age : int 22 26 24 27 33 22 26 28 26 NA ...
## $ Class/Dept: chr "A la Carte" "A la Carte" "A la Carte" "A la Carte" ...
## $ Ticket : chr NA NA NA NA ...
## $ Fare : chr NA NA NA NA ...
## $ Group : chr NA NA NA NA ...
## $ Ship : chr NA NA NA NA ...
## $ Joined : chr "Southampton" "Southampton" "Southampton" "Southampton" ...
## $ Job : chr "Assistant Waiter" "Assistant Plateman" "Waiter" "Waiter" ...
## $ Boat : chr NA NA NA NA ...
## $ Body : int 221 NA NA NA NA 215 NA NA NA NA ...
## $ : chr NA NA NA NA ...
## $ : chr NA NA NA NA ...
## 'data.frame': 8 obs. of 11 variables:
## $ Name : chr "BRAILEY, Mr W. Theodore Ronald" "BRICOUX, Mr Roger Marie" "CLARKE, Mr John Frederick Preston" "HARTLEY, Mr Wallace Henry" ...
## $ Age : int 24 20 30 33 21 23 32 32
## $ Class/Dept: chr "2nd Class" "2nd Class" "2nd Class" "2nd Class" ...
## $ Ticket : int 250654 250654 250654 250654 250654 250654 250654 250654
## $ Fare : chr NA NA NA NA ...
## $ Group : chr "Musician" "Musician" "Musician" "Musician" ...
## $ Ship : chr NA NA NA NA ...
## $ Joined : chr "Southampton" "Southampton" "Southampton" "Southampton" ...
## $ Job : chr "Musician" "Musician" "Musician" "Musician" ...
## $ Boat : chr NA NA NA NA ...
## $ Body : int NA NA 202 224 193 NA NA NA
## 'data.frame': 431 obs. of 13 variables:
## $ Name : chr "ABBOTT, Mr Ernest Owen" "AHIER, Mr Percy Snowden" "AKERMAN, Mr Albert" "AKERMAN, Mr Joseph Francis" ...
## $ Age : int 21 20 28 35 36 26 17 41 48 19 ...
## $ Class/Dept: chr "Victualling" "Victualling" "Victualling" "Victualling" ...
## $ Ticket : chr NA NA NA NA ...
## $ Fare : chr NA NA NA NA ...
## $ Group : chr NA NA NA NA ...
## $ Ship : chr NA NA NA NA ...
## $ Joined : chr "Southampton" "Southampton" "Southampton" "Southampton" ...
## $ Job : chr "Lounge Pantry Steward" "Saloon Steward" "Steward" "Assistant Pantryman Steward" ...
## $ Boat : chr NA NA NA NA ...
## $ Body : int NA NA NA 205 NA NA NA NA 146 NA ...
## $ : chr NA NA NA NA ...
## $ : chr NA NA NA NA ...
## $`1er_clase.rds`
## NULL
##
## $`2da_clase.rds`
## NULL
##
## $`3era_clase.rds`
## NULL
##
## $deck.rds
## NULL
##
## $engine.rds
## NULL
##
## $guarantee_group.rds
## NULL
##
## $postal_clerk.rds
## NULL
##
## $restaurant.rds
## NULL
##
## $ships_orchesta.rds
## NULL
##
## $victualling.rds
## NULL
(╯°□°)╯︵ ┻━┻ Era demasiado bello para ser real! Age y Body en algunos dataframes son int en algunos son chr… no los podemos juntar si son de diferentes tipos cambiemos a chr todas las columnas por facilidad
ds <- lapply(ds, function(x) lapply(x, as.character))
#verifiquemos
lapply(ds, str)## List of 13
## $ Name : chr [1:324] "ALLEN, Miss Elisabeth Walton" "ALLISON, Mr Hudson Joshua Creighton" "ALLISON, Mrs Bessie Waldo" "ALLISON, Miss Helen Loraine" ...
## $ Age : chr [1:324] "29" "30" "25" "2" ...
## $ Class/Dept: chr [1:324] "1st Class" "1st Class" "1st Class" "1st Class" ...
## $ Ticket : chr [1:324] "24160" "113781" "113781" "113781" ...
## $ Fare : chr [1:324] "£211 6s 9d" "£151 16s" "£151 16s" "£151 16s" ...
## $ Group : chr [1:324] NA NA NA NA ...
## $ Ship : chr [1:324] NA NA NA NA ...
## $ Joined : chr [1:324] "Southampton" "Southampton" "Southampton" "Southampton" ...
## $ Job : chr [1:324] NA "Businessman" NA NA ...
## $ Boat : chr [1:324] "2" NA NA NA ...
## $ Body : chr [1:324] NA "135" NA NA ...
## $ : chr [1:324] NA NA NA NA ...
## $ : chr [1:324] NA NA NA NA ...
## List of 13
## $ Name : chr [1:285] "ABELSON, Mr Samuel" "ABELSON, Mrs" "ALDWORTH, Mr Charles Augustus" "ANDREW, Mr Edgar Samuel" ...
## $ Age : chr [1:285] "30" "28" "30" "17" ...
## $ Class/Dept: chr [1:285] "2nd Class" "2nd Class" "2nd Class" "2nd Class" ...
## $ Ticket : chr [1:285] "3381" "3381" "248744" "231945" ...
## $ Fare : chr [1:285] "£24" "£24" "£13" "£11 10s" ...
## $ Group : chr [1:285] NA NA "Servant" NA ...
## $ Ship : chr [1:285] NA NA NA NA ...
## $ Joined : chr [1:285] "Cherbourg" "Cherbourg" "Southampton" "Southampton" ...
## $ Job : chr [1:285] NA NA "Chauffeur" NA ...
## $ Boat : chr [1:285] NA "10" NA NA ...
## $ Body : chr [1:285] NA NA NA NA ...
## $ : chr [1:285] NA NA NA NA ...
## $ : chr [1:285] NA NA NA NA ...
## List of 11
## $ Name : chr [1:708] "ABBING, Mr Anthony" "ABBOTT, Mrs Rhoda Mary 'Rosa'" "ABBOTT, Mr Rossmore Edward" "ABBOTT, Mr Eugene Joseph" ...
## $ Age : chr [1:708] "42" "39" "16" "14" ...
## $ Class/Dept: chr [1:708] "3rd Class" "3rd Class" "3rd Class" "3rd Class" ...
## $ Ticket : chr [1:708] "5547" "CA2673" "CA2673" "CA2673" ...
## $ Fare : chr [1:708] "£7 11s" "£20 5s" "£20 5s" "£20 5s" ...
## $ Group : chr [1:708] NA NA NA NA ...
## $ Ship : chr [1:708] NA NA NA NA ...
## $ Joined : chr [1:708] "Southampton" "Southampton" "Southampton" "Southampton" ...
## $ Job : chr [1:708] "Blacksmith" NA "Jeweller" "Scholar" ...
## $ Boat : chr [1:708] NA "A" NA NA ...
## $ Body : chr [1:708] NA NA "190" NA ...
## List of 11
## $ Name : chr [1:66] "ANDERSON, Mr James" "ARCHER, Mr Ernest Edward" "BAILEY, Mr Henry Joseph" "BOXHALL, Mr Joseph Groves" ...
## $ Age : chr [1:66] "40" "36" "43" "28" ...
## $ Class/Dept: chr [1:66] "Deck" "Deck" "Deck" "Deck" ...
## $ Ticket : chr [1:66] NA NA NA NA ...
## $ Fare : chr [1:66] NA NA NA NA ...
## $ Group : chr [1:66] NA NA NA NA ...
## $ Ship : chr [1:66] NA NA NA NA ...
## $ Joined : chr [1:66] "Southampton" "Southampton" "Southampton" "Belfast" ...
## $ Job : chr [1:66] "Able Seaman" "Able Seaman" "Master-at-arms" "4th. Officer" ...
## $ Boat : chr [1:66] "3" "16" "16" "2" ...
## $ Body : chr [1:66] NA NA NA NA ...
## List of 11
## $ Name : chr [1:325] "ABRAMS, Mr William" "ADAMS, Mr R." "ALLEN, Mr Henry" "ALLEN, Mr Ernest Frederick" ...
## $ Age : chr [1:325] "33" "26" "32" "24" ...
## $ Class/Dept: chr [1:325] "Engine" "Engine" "Engine" "Engine" ...
## $ Ticket : chr [1:325] NA NA NA NA ...
## $ Fare : chr [1:325] NA NA NA NA ...
## $ Group : chr [1:325] NA NA NA NA ...
## $ Ship : chr [1:325] NA NA NA NA ...
## $ Joined : chr [1:325] "Southampton" "Southampton" "Southampton" "Southampton" ...
## $ Job : chr [1:325] "Fireman / Stoker" "Fireman / Stoker" "Fireman / Stoker" "Trimmer" ...
## $ Boat : chr [1:325] NA NA NA "B" ...
## $ Body : chr [1:325] NA NA "145" NA ...
## List of 11
## $ Name : chr [1:9] "ANDREWS, Mr Thomas" "CAMPBELL, Mr William Henry" "CHISHOLM, Mr Roderick Robert Crispin" "CUNNINGHAM, Mr Alfred Fleming" ...
## $ Age : chr [1:9] "39" "21" "40" "21" ...
## $ Class/Dept: chr [1:9] "1st Class" "2nd Class" "1st Class" "2nd Class" ...
## $ Ticket : chr [1:9] "112050" "239853" "112051" "239853" ...
## $ Fare : chr [1:9] NA NA NA NA ...
## $ Group : chr [1:9] "H&W Guarantee Group" "H&W Guarantee Group" "H&W Guarantee Group" "H&W Guarantee Group" ...
## $ Ship : chr [1:9] NA NA NA NA ...
## $ Joined : chr [1:9] "Belfast" "Belfast" "Belfast" "Belfast" ...
## $ Job : chr [1:9] "Shipbuilder" NA "Draughtsman" "Fitter" ...
## $ Boat : chr [1:9] NA NA NA NA ...
## $ Body : chr [1:9] NA NA NA NA ...
## List of 13
## $ Name : chr [1:15] "GWYNN, Mr William Logan" "MARCH, Mr John Starr" "SMITH, Mr John Richard Jago" "WILLIAMSON, Mr James Bertram" ...
## $ Age : chr [1:15] "37" "50" "35" "35" ...
## $ Class/Dept: chr [1:15] "Victualling" "Victualling" "Victualling" "Victualling" ...
## $ Ticket : chr [1:15] NA NA NA NA ...
## $ Fare : chr [1:15] NA NA NA NA ...
## $ Group : chr [1:15] "Postal Clerk" "Postal Clerk" "Postal Clerk" "Postal Clerk" ...
## $ Ship : chr [1:15] NA NA NA NA ...
## $ Joined : chr [1:15] "Southampton" "Southampton" "Southampton" "Southampton" ...
## $ Job : chr [1:15] "Postal Clerk" "Postal Clerk" "Postal Clerk" "Postal Clerk" ...
## $ Boat : chr [1:15] NA NA NA NA ...
## $ Body : chr [1:15] NA "225" NA NA ...
## $ : chr [1:15] NA NA NA NA ...
## $ : chr [1:15] NA NA NA NA ...
## List of 13
## $ Name : chr [1:69] "ALLARIA, Sig. Battista Antonio" "ASPESLAGH, Mr Georges" "BANFI, Sig. Ugo" "BASILICO, Sig. Giovanni" ...
## $ Age : chr [1:69] "22" "26" "24" "27" ...
## $ Class/Dept: chr [1:69] "A la Carte" "A la Carte" "A la Carte" "A la Carte" ...
## $ Ticket : chr [1:69] NA NA NA NA ...
## $ Fare : chr [1:69] NA NA NA NA ...
## $ Group : chr [1:69] NA NA NA NA ...
## $ Ship : chr [1:69] NA NA NA NA ...
## $ Joined : chr [1:69] "Southampton" "Southampton" "Southampton" "Southampton" ...
## $ Job : chr [1:69] "Assistant Waiter" "Assistant Plateman" "Waiter" "Waiter" ...
## $ Boat : chr [1:69] NA NA NA NA ...
## $ Body : chr [1:69] "221" NA NA NA ...
## $ : chr [1:69] NA NA NA NA ...
## $ : chr [1:69] NA NA NA NA ...
## List of 11
## $ Name : chr [1:8] "BRAILEY, Mr W. Theodore Ronald" "BRICOUX, Mr Roger Marie" "CLARKE, Mr John Frederick Preston" "HARTLEY, Mr Wallace Henry" ...
## $ Age : chr [1:8] "24" "20" "30" "33" ...
## $ Class/Dept: chr [1:8] "2nd Class" "2nd Class" "2nd Class" "2nd Class" ...
## $ Ticket : chr [1:8] "250654" "250654" "250654" "250654" ...
## $ Fare : chr [1:8] NA NA NA NA ...
## $ Group : chr [1:8] "Musician" "Musician" "Musician" "Musician" ...
## $ Ship : chr [1:8] NA NA NA NA ...
## $ Joined : chr [1:8] "Southampton" "Southampton" "Southampton" "Southampton" ...
## $ Job : chr [1:8] "Musician" "Musician" "Musician" "Musician" ...
## $ Boat : chr [1:8] NA NA NA NA ...
## $ Body : chr [1:8] NA NA "202" "224" ...
## List of 13
## $ Name : chr [1:431] "ABBOTT, Mr Ernest Owen" "AHIER, Mr Percy Snowden" "AKERMAN, Mr Albert" "AKERMAN, Mr Joseph Francis" ...
## $ Age : chr [1:431] "21" "20" "28" "35" ...
## $ Class/Dept: chr [1:431] "Victualling" "Victualling" "Victualling" "Victualling" ...
## $ Ticket : chr [1:431] NA NA NA NA ...
## $ Fare : chr [1:431] NA NA NA NA ...
## $ Group : chr [1:431] NA NA NA NA ...
## $ Ship : chr [1:431] NA NA NA NA ...
## $ Joined : chr [1:431] "Southampton" "Southampton" "Southampton" "Southampton" ...
## $ Job : chr [1:431] "Lounge Pantry Steward" "Saloon Steward" "Steward" "Assistant Pantryman Steward" ...
## $ Boat : chr [1:431] NA NA NA NA ...
## $ Body : chr [1:431] NA NA NA "205" ...
## $ : chr [1:431] NA NA NA NA ...
## $ : chr [1:431] NA NA NA NA ...
## $`1er_clase.rds`
## NULL
##
## $`2da_clase.rds`
## NULL
##
## $`3era_clase.rds`
## NULL
##
## $deck.rds
## NULL
##
## $engine.rds
## NULL
##
## $guarantee_group.rds
## NULL
##
## $postal_clerk.rds
## NULL
##
## $restaurant.rds
## NULL
##
## $ships_orchesta.rds
## NULL
##
## $victualling.rds
## NULL
#bind_rows es como rbind solo que optimizado por Hadley Wickham :)
titanic <- bind_rows(ds)[, 1:num_cols]Cambiemos nombres a minúmsculas y sin simbolillos raros /
names(titanic) <- str_replace_all(names(titanic), "/| ", "_") %>%
str_to_lower()
names(titanic)## [1] "name" "age" "class_dept" "ticket" "fare"
## [6] "group" "ship" "joined" "job" "boat"
## [11] "body"
Pasemos el dataframe a un objeto más eficiente
titanic <- tbl_df(titanic)
titanicsurvived, name, last_name, sex ¿Se te ocurre alguna forma de definir survived?m…Haremos el 3 y el 4 juntos :)
titanic <- titanic %>%
separate(name, into=c("last_name", "name"), sep=",", extra="drop") %>%
separate(fare, into=c("pounds", "shillings", "pence"), sep=" ", extra="drop") %>%
separate(age, into=c("age", "units"), sep=2, extra="drop") %>%
mutate(sex=ifelse(grepl("Miss|Mrs|Mme.|Lady|Doña|Ms", name), 'F',
ifelse(grepl("Mr|Sir|Sig|Dr|Master|Captain|Major|Rev.|Colonel|Fr|Don.", name), 'M', NA))) %>%
mutate(boat_location=ifelse(as.integer(boat) %in% c(9:16), 'Popa',
ifelse(boat %in% c(LETTERS[1:4]) | as.integer(boat) %in% c(1:8), 'Proa', NA))) %>%
mutate(age=ifelse(units == "m", 1, as.integer(age))) %>%
mutate(survived=!is.na(boat)) %>%
dplyr::select(-c(shillings, pence, body, units)) %>%
mutate(pounds=str_replace(pounds, "£", "") %>% as.integer()) %>%
mutate(class_dept=as.factor(class_dept), group=as.factor(group), ship=as.factor(ship),
joined=as.factor(joined), job=as.factor(job), boat=as.factor(boat),
sex=as.factor(sex), boat_location=as.factor(boat_location))## Warning: Too few values at 709 locations: 2, 3, 4, 5, 6, 13, 14, 15, 23,
## 27, 30, 31, 32, 34, 35, 37, 38, 40, 41, 45, ...
## Warning in as.integer(boat) %in% c(9:16): NAs introduced by coercion
## Warning in as.integer(boat) %in% c(1:8): NAs introduced by coercion
## Warning in ifelse(units == "m", 1, as.integer(age)): NAs introduced by
## coercion
Que bonito es dplyr y tidyr (ノ^_^)ノ
summary(titanic)## last_name name age class_dept
## Length:2240 Length:2240 Min. : 1.00 3rd Class :708
## Class :character Class :character 1st Qu.:22.00 Victualling:436
## Mode :character Mode :character Median :29.00 1st Class :327
## Mean :30.02 Engine :325
## 3rd Qu.:36.75 2nd Class :299
## Max. :74.00 (Other) :135
## NA's :26 NA's : 10
## ticket pounds group
## Length:2240 Min. : 3.00 H&W Guarantee Group: 18
## Class :character 1st Qu.: 7.00 Musician : 16
## Mode :character Median : 14.00 Postal Clerk : 10
## Mean : 33.32 Servant : 43
## 3rd Qu.: 31.00 NA's :2153
## Max. :512.00
## NA's :949
## ship joined job boat
## NA's:2240 Belfast : 207 Fireman / Stoker: 161 13 : 52
## Cherbourg : 274 General Labourer: 161 15 : 43
## Queenstown : 120 Saloon Steward : 127 C : 42
## Southampton:1626 Trimmer : 73 14 : 38
## NA's : 13 Farm Labourer : 49 4 : 38
## (Other) :1011 (Other): 369
## NA's : 658 NA's :1658
## sex boat_location survived
## F : 484 Popa: 280 Mode :logical
## M :1742 Proa: 302 FALSE:1658
## NA's: 14 NA's:1658 TRUE :582
##
##
##
##
age que sea categórica. Definamos 3 categorías:titanic <- titanic %>% mutate(age = ifelse(age <= 18, "infante",
ifelse(age > 65, "adulto mayor",
"adulto")))
#verifiquemos
titanicggplot(titanic, aes(pounds)) +
geom_histogram(binwidth = 30, na.rm=T) +
theme_bw()titanic <- titanic %>%
group_by(ticket) %>%
mutate(pounds_per_ticket = round(pounds/n())) %>%
ungroup()
titanictitanic %>% filter(class_dept %in% c('1st Class', '2nd Class', '3rd Class')) %>%
ggplot(aes(pounds_per_ticket)) +
geom_histogram(binwidth = 10) +
facet_grid(class_dept~., scales = "free_y") +
theme_bw()## Warning: Removed 43 rows containing non-finite values (stat_bin).
\(\rightarrow\) Aproximadamente 10 libras de 1912 son 1,080 libras actuales, 50 libras son 5,400 y 100 libras son 10,800 libras al 2017
titanic %>%
group_by(boat_location) %>%
summarise(n=n())Eso no dice mucho…
titanic %>%
group_by(boat) %>%
summarise(n=n()) %>%
arrange(desc(n))Los botes del 1 al 16 tenían una capacidad de 65 personas, los botes del A al D tenían una capacidad de 45 personas. (Fuente).
Fuente de los datos:
instalar <- function(paquete) {
if (!require(paquete,character.only = TRUE, quietly = TRUE, warn.conflicts = FALSE)) {
install.packages(as.character(paquete), dependecies = TRUE, repos = "http://cran.us.r-project.org")
library(paquete, character.only = TRUE, quietly = TRUE, warn.conflicts = FALSE)
}
}
paquetes <- c('lubridate', 'magrittr', 'ggvis', 'dplyr', 'tidyr', 'readr', 'rvest',
'ggplot2', 'stringr', 'ggthemes', 'googleVis', 'shiny', 'tibble', 'vcd', 'vcdExtra',
'GGally', 'readODS', 'readxl', "RSQLite")
lapply(paquetes, instalar);Los datos están en http://sorry.vse.cz/~berka/challenge/pkdd1999/data_berka.zip, descárgalos y guárdalos en data/berka, fueron usados en la competencia de PKDD de 1999.
“El banco busca mejorar sus servicios, Por ejemplo, los admnistradores del banco tienen únicamente una vaga idea de quién es un buen cliente (al cual ofrecerle más servicios) y quién es un mal cliente (alguien al que hay que cuidar para minimizar las pérdidas del banco). Afortunadamente, el banco almacena datos de sus clientes, sus cuentas (transacciones), préstamos que ya se otorgaron, tarjetas de crédito dadas. Los administradaores del banco esperan mejorar su entendimiento de los clientes y mejorar así sus servicios. Una mera aplicación de una herramienta de discovery no los va a convencer.”
Características de la cuenta Archivo: account
| Column | Description | Notes |
|---|---|---|
| account_id | Identification of the account | |
| district_id | Location of the branch | |
| date | Date of the account’s creation | In the form: YYMMDD |
| frequency | Frequency of statement issuance | “POPLATEK MESICNE” - Monthly Issuance |
| “POPLATEK TYDNE” - Weekly Issuance | ||
| “POPLATEK PO OBRATU” - Issuance After Transaction |
Características del cliente Archivo: client
| Column | Description | Notes |
|---|---|---|
| client_id | Client Identifier | |
| birth number | Birthday and Sex | The value is in the form: YYMMDD (for men) |
| The value is in the form: YYMM+50DD (for women) | ||
| Where YYMMDD is the date of birth | ||
| district_id | Address of the client |
Relaciona una cuenta con un cliente (los derechos de los clientes para operar cuentas) Archivo: disp
| Column | Description | Notes |
|---|---|---|
| disp_id | Record Identifier | |
| client_id | Client Identifier | |
| account_id | Account Identifier | |
| type | Type of Disposition (owner/user) | Only owner can issue permanent orders and ask for a loan |
Préstamos dabos a una cuenta Archivo: loan
| Column | Description | Notes |
|---|---|---|
| disp_id | Record Identifier | |
| loan_id | Record Identifier | |
| account_id | Account Identifier | |
| date | Date when loan was granted | In the form: YYMMDD |
| amount | Amount of Loan | |
| duration | Duration of Loan | |
| payments | Monthly Payments on Loan | |
| status | Status in paying off the loan | ‘A’ stands for contract finished, no problems |
| ‘B’ stands for contract finished, loan not payed | ||
| ‘C’ stands for running contract, OK thus-far | ||
| ‘D’ stands for running contract, client in debt |
Características de una orden de pago Archivo: order
| Column | Description | Notes |
|---|---|---|
| order_id | Record Identifier | |
| account_id | Account the order is issued for | |
| bank_to | Bank of the recipient | Each bank has a unique two-letter code |
| account_to | Account of the recipient | |
| amount | Amount debited from order account | |
| K_symbol | Characterization of the payment | ‘POJISTNE’ stands for Insurance Payment |
| ‘SIPO’ stands for Household Payment | ||
| ‘LEASING’ stands for Leasing Payment | ||
| ‘UVER’ stands for Loan Payment |
Transacciones en las cuentas Archivo: trans
| Column | Description | Notes |
|---|---|---|
| trans_id | Record Identifier | |
| account_id | Account the transaction is issued on | |
| date | Date of transaction | In the form: YYMMDD |
| type | debit/credit transaction | ‘PRIJEM’ stands for Credit |
| ‘VYDAJ’ stands for Debit (withdrawal) | ||
| operation | Mode of Transaction | ‘VYBER KARTOU’ stands for Credit Card Withdrawal |
| ‘VKLAD’ stands for Credit in Cash | ||
| ‘PREVOD Z UCTU’ stands for Collection from Another Bank | ||
| ‘VYBER’ stands for Withdrawal in Cash | ||
| ‘PREVOD NA UCET’ stands for Remittance to Another Bank | ||
| amount | Amount of Transaction | |
| balance | Balance of Account after Transaction | |
| K_Symbol | Characterization of Transaction | ‘POJISTNE’ stands for Insurance Payment |
| ‘SLUZBY’ stands for Payment of Statement | ||
| ‘UROK’ stands for Interest Credited | ||
| ‘SANKC. UROK’ stands for Sanction Interest if Negative Balance | ||
| ‘SIPO’ stands for Household Payment | ||
| ‘DUCHOD’ stands for Old-age Pension Payment | ||
| ‘UVER’ stands for Loan Payment | ||
| bank | Bank of the partner | Each bank has unique two-letter code |
| account | Account of the partner |
Características de un distrito Archivo: district
| Column | Description | Notes |
|---|---|---|
| A1 | district_id | District Identifier |
| A2 | District Name | |
| A3 | Region | |
| A4 | No. of Inhabitants | |
| A5 | No. of Municipalities with inhabitants < 499 | |
| A6 | No. of Municipalities with inhabitants 500-1999 | |
| A7 | No. of Municipalities with inhabitants 2000-9999 | |
| A8 | No. of Municipalities with inhabitants > 10000 | |
| A9 | No. of Cities | |
| A10 | Ratio of urban inhabitants | |
| A11 | Average Salary | |
| A12 | Unemployment rate in 1995 | |
| A13 | Unemployment rate in 1996 | |
| A14 | No. of Enterpreneurs per 1000 inhabitants | |
| A15 | No. of Crimes commited in 1995 | |
| A16 | No. of Crimes commited in 1996 |
Tarjeta de crédito otorgada a una cuenta Archivo: card
| Column | Description | Notes |
|---|---|---|
| card_id | Card Identifier | |
| disp_id | Disposition to an account | |
| type | Type of card | Types are ‘Junior’, ‘Classic’, and ‘Gold’ |
| issued | Date card was issued | In the format: YYMMDD |
account) y dinámicas (pagos, créditos, balances) dados en order y transactionclient características de personas que pueden manipular cuentas.dispositionloan y credit_cardBerka dataset
La utilización de una base de datos (Relacional, Grafos, Columnar, etc), siempre es recomendable sobre el uso de archivos (la excepción es un Apache Hadoop, pero eso lo veremos en Métodos de Gran Escala ╭(◔ ◡ ◔)/).
Las ventajas de la utilización de una base de datos son las siguientes:
SQL)Usaremos sqlite3 para este ejemplo. SQLite es una base de datos relacional, local (por lo que algunas de las ventajas recién mencionadas no aplican ¿Cuál?)
sudo apt-get update
sudo apt-get install sqlite3
Instalar SQLite3 en MAC: si tienes OS Leopard en adelante, no necistas instalarlo, ya viene. Si tienes un OS más viejito Mac y Windows -nunca lo he hecho!-
NOTA: Lo que sigue ocurre en la línea de comandos
Para abrir una base de datos sqlite en memoria
$ sqlite3
SQLite version 3.13.0 2016-05-18 10:57:30
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite>
## Emebelliciendo el output
sqlite> .header on
sqlite> .mode column
## Importar archivos (accounts.asc) a la base de datos a la tabla (accounts)
## ¡Usa plural para las tablas!
sqlite> .separator ";"
sqlite> .import account.asc accounts
## Verifica que la tabla exista
sqlite> .tables
acccounts
## Estructura de la tabla
sqrlite> PRAGMA table_info(accounts);
cid name type notnull dflt_value pk
---------- ---------- ---------- ---------- ---------- ----------
0 account_id TEXT 0 0
1 district_id TEXT 0 0
2 frequency TEXT 0 0
3 date TEXT 0 0
## veamos qué hay adentro
sqrlite> select * from accounts limit 5;
account_id district_id frequency date
---------- ----------- ---------------- ----------
576 55 POPLATEK MESICNE 930101
3818 74 POPLATEK MESICNE 930101
704 55 POPLATEK MESICNE 930101
2378 16 POPLATEK MESICNE 930101
2632 24 POPLATEK MESICNE 930102
## Guardar la tabla
sqlite> .save berka.raw
La siguiente vez que quieras consultar la base de datos
$ sqlite3 berka.raw
SQLite version 3.11.0 2016-02-15 17:29:24
Enter ".help" for usage hints.
sqlite>
O si se te olvida poner el nombre del archivo:
$ sqlite3
SQLite version 3.11.0 2016-02-15 17:29:24
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .open berka.raw
Ejercicio: To Raw
Cargaremos los datos en berka.raw
for db in *asc;
do
table=${db%.*}s
if [ "$db" = "trans.asc" ]; then
table="transactions"
fi
echo -e ".separator ';'\n.import ${db} ${table}" | sqlite3 berka.raw
done
Puedes conectarte a tu base de sqlite con dplyr , necesitarás el paquete RSQLite
library(RSQLite)
berka_db <- src_sqlite(path="~/Documents/itam/introduction_to_ds/intro_to_ds/data/berka/berka.raw",
create=FALSE)
db_list_tables(berka_db$con)## [1] "accounts" "cards" "clients" "disps"
## [5] "districts" "loans" "orders" "transactions"
accounts_tbl <- tbl(berka_db, "accounts")
clients_tbl <- tbl(berka_db, "clients")
dispositions_tbl <- tbl(berka_db, "disps")
accounts_tbl %>% group_by(district_id) %>%
summarise(count=n()) %>%
arrange(desc(count)) %>%
collect()Ejercicio: Raw to Clean
account tenga un ownerEn la tabla disposition viene el atributo type que puede tener 2 valores distintos: OWNER y DISPONENT
sqlite3> select distinct(type)
from disps;
O en dplyr:
dispositions_tbl %>% distinct(type) %>% collect()sqlite3> select *, count(*)
from disps
where type = 'ONWER'
group by account_id
having count(*) > 1
order by count(*) desc
limit 5;
dispositions_tbl %>% filter(type == 'OWNER') %>%
group_by(account_id) %>%
summarise(n=n()) %>%
arrange(desc(n)) %>% collect()Los registros de orders y loans están duplicados en transactions. Es decir los regsitros de ordery loan están dentro de transactions (por ejemplo, Los registros de loan en tran están identificados por el k_symbol LP)
client cambia BirthNumber a sex y ageage en Youth (0-24), Adult (25-45), Middle-age (46-64) y Senior (> 65)disposition cambia de Dispondent -> Userloan discretiza usando alguna heuristica amount, duration y paymentstransaction traduce la columna type, operation, k_symbol*_id a singular.Guardemos los datos en berka.clean
Imágenes tomadas de TAMR
Un aspecto que siempre es olvidado, o que no se considera importante debido a los blogs, es el versionado de control de los datos
Esto se puede implementar, agregando columnas para indicar de donde vienen los datos, o con qué procedimiento de limpieza se generaron, etc.
Se puede utilizar el mismo id del código del ETL guardado en github. ¿Cómo implementarías esto? ¿Puedes trazar de dónde provienen tus datos?